From 22c7a94616646a92cb65fbce7c3a6342c824f373 Mon Sep 17 00:00:00 2001 From: Theo2903 Date: Sun, 27 Apr 2025 13:03:29 +0200 Subject: [PATCH] Enhance button component documentation and implementation. Added new scripts to package.json for registry build and documentation generation. Updated button component to use React Native's Pressable and improved styling variants. Removed outdated button.mdx file. --- .../content/components/button/button.mdx | 135 ------------ app/(site)/docs/components/button/page.tsx | 150 ++++++------- package.json | 7 +- pnpm-lock.yaml | 112 ++++++++++ public/r/button.json | 5 +- registry/button/button.tsx | 76 ++++--- scripts/README.md | 48 ++++ scripts/build-registry.js | 76 +++++++ scripts/generate-component-docs.js | 205 ++++++++++++++++++ scripts/update-components.js | 25 +++ 10 files changed, 591 insertions(+), 248 deletions(-) delete mode 100644 app/(site)/content/components/button/button.mdx create mode 100644 scripts/README.md create mode 100644 scripts/build-registry.js create mode 100644 scripts/generate-component-docs.js create mode 100644 scripts/update-components.js diff --git a/app/(site)/content/components/button/button.mdx b/app/(site)/content/components/button/button.mdx deleted file mode 100644 index ed4241f..0000000 --- a/app/(site)/content/components/button/button.mdx +++ /dev/null @@ -1,135 +0,0 @@ ---- -title: "Button" -description: "A button component that can be used to trigger actions or navigate to different pages." ---- - -import { ComponentPreview } from "@/components/docs/component-preview" - -# Button - -A button component that can be used to trigger actions or navigate to different pages. - - - Click me - - ); -}`, - language: "tsx", - }, - { - title: "Variants", - value: "variants", - content: `import { Button } from "@nativeui/ui"; - -export default function ButtonVariants() { - return ( -
- - - - - - -
- ); -}`, - language: "tsx", - }, - { - title: "Sizes", - value: "sizes", - content: `import { Button } from "@nativeui/ui"; - -export default function ButtonSizes() { - return ( -
- - - - -
- ); -}`, - language: "tsx", - } - ]} - componentCode={`import * as React from "react"; -import { Pressable, Text } from "react-native"; -import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils"; - -const buttonVariants = cva( - "flex items-center justify-center rounded-md", - { - variants: { - variant: { - default: "bg-primary", - destructive: "bg-destructive", - outline: "border border-input bg-background", - secondary: "bg-secondary", - ghost: "hover:bg-accent hover:text-accent-foreground", - link: "text-primary underline-offset-4 hover:underline", - }, - size: { - default: "h-10 px-4 py-2", - sm: "h-9 rounded-md px-3", - lg: "h-11 rounded-md px-8", - icon: "h-10 w-10", - }, - }, - defaultVariants: { - variant: "default", - size: "default", - }, - } -); - -export interface ButtonProps - extends React.ComponentPropsWithoutRef, - VariantProps { - asChild?: boolean; -} - -const Button = React.forwardRef< - React.ElementRef, - ButtonProps ->(({ className, variant, size, asChild = false, ...props }, ref) => { - return ( - - ); -}); -Button.displayName = "Button"; - -export { Button, buttonVariants };`} - previewCode={`import { Button } from "@nativeui/ui"; - -export default function ButtonDemo() { - return ( -
- - - - - - -
- ); -}`} - registryName="button" - packageName="@nativeui/ui" -/> diff --git a/app/(site)/docs/components/button/page.tsx b/app/(site)/docs/components/button/page.tsx index 6040c2a..ae30f27 100644 --- a/app/(site)/docs/components/button/page.tsx +++ b/app/(site)/docs/components/button/page.tsx @@ -4,81 +4,60 @@ export default function ButtonPage() { return ( - Click me - - ); -}`, - language: "tsx", - }, - { - title: "Variants", - value: "variants", - content: `import { Button } from "@nativeui/ui"; - -export default function ButtonVariants() { - return ( -
- - - - - - -
- ); -}`, - language: "tsx", - }, - { - title: "Sizes", - value: "sizes", - content: `import { Button } from "@nativeui/ui"; - -export default function ButtonSizes() { - return ( -
- - - - -
- ); -}`, - language: "tsx", - }, - ]} + { + "title": "Default", + "value": "default", + "content": "import { Button } from \"@nativeui/ui\";\n\nexport default function ButtonDemo() {\n return (\n \n );\n}", + "language": "tsx" + }, + { + "title": "Variants", + "value": "variants", + "content": "import { Button } from \"@nativeui/ui\";\n\nexport default function ButtonVariants() {\n return (\n
\n \n \n \n \n \n \n \n
\n );\n}", + "language": "tsx" + }, + { + "title": "Sizes", + "value": "sizes", + "content": "import { Button } from \"@nativeui/ui\";\n\nexport default function ButtonSizes() {\n return (\n
\n \n \n \n \n
\n );\n}", + "language": "tsx" + } +]} componentCode={`import * as React from "react"; -import { Pressable, Text } from "react-native"; -import { cva, type VariantProps } from "class-variance-authority"; +import { + Pressable, + PressableProps as RNPressableProps, + View, + ViewStyle, + PressableStateCallbackType, +} from "react-native"; import { cn } from "@/lib/utils"; -const buttonVariants = cva( - "flex items-center justify-center rounded-md", +import { cva, type VariantProps } from "class-variance-authority"; + +export const buttonVariants = cva( + "flex-row items-center justify-center rounded-md", { variants: { variant: { - default: "bg-primary", - destructive: "bg-destructive", - outline: "border border-input bg-background", - secondary: "bg-secondary", - ghost: "hover:bg-accent hover:text-accent-foreground", - link: "text-primary underline-offset-4 hover:underline", + default: + "bg-primary text-primary-foreground dark:bg-primary dark:text-primary-foreground shadow", + destructive: + "bg-destructive text-destructive-foreground dark:bg-destructive dark:text-destructive-foreground shadow-sm", + outline: + "border border-input bg-background text-foreground dark:border-input dark:bg-background dark:text-foreground shadow-sm", + secondary: + "bg-secondary text-secondary-foreground dark:bg-secondary dark:text-secondary-foreground shadow-sm", + ghost: "text-foreground dark:text-foreground", + link: "text-primary dark:text-primary underline", }, size: { - default: "h-10 px-4 py-2", - sm: "h-9 rounded-md px-3", - lg: "h-11 rounded-md px-8", - icon: "h-10 w-10", + default: "h-12 px-6", + sm: "h-10 px-4", + lg: "h-14 px-8", + icon: "h-12 w-12", }, }, defaultVariants: { @@ -89,26 +68,37 @@ const buttonVariants = cva( ); export interface ButtonProps - extends React.ComponentPropsWithoutRef, + extends Omit, VariantProps { + className?: string; + style?: ViewStyle; asChild?: boolean; } -const Button = React.forwardRef< - React.ElementRef, - ButtonProps ->(({ className, variant, size, asChild = false, ...props }, ref) => { - return ( - - ); -}); +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, children, ...props }, ref) => { + return ( + + {(state: PressableStateCallbackType) => ( + + {typeof children === "function" ? children(state) : children} + + )} + + ); + } +); + Button.displayName = "Button"; -export { Button, buttonVariants };`} +export { Button }; +`} previewCode={`import { Button } from "@nativeui/ui"; export default function ButtonDemo() { diff --git a/package.json b/package.json index 061aaea..3c7cfc4 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,11 @@ "build": "next build", "start": "next start", "lint": "next lint", - "registry:build": "shadcn build" + "registry:build": "shadcn build", + "build:registry": "node scripts/build-registry.js", + "generate:docs": "node scripts/generate-component-docs.js", + "update:components": "node scripts/update-components.js", + "create:component": "node scripts/create-component.js" }, "dependencies": { "@mdx-js/loader": "^3.1.0", @@ -56,6 +60,7 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "15.2.3", + "glob": "^11.0.2", "shadcn": "2.4.0-canary.17", "tailwindcss": "^4", "typescript": "^5" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7afda0f..a35a1d2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -141,6 +141,9 @@ importers: eslint-config-next: specifier: 15.2.3 version: 15.2.3(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2) + glob: + specifier: ^11.0.2 + version: 11.0.2 shadcn: specifier: 2.4.0-canary.17 version: 2.4.0-canary.17(@types/node@20.17.24)(typescript@5.8.2) @@ -502,6 +505,10 @@ packages: '@types/node': optional: true + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + '@jridgewell/gen-mapping@0.3.8': resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} engines: {node: '>=6.0.0'} @@ -1229,6 +1236,10 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -1571,6 +1582,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + electron-to-chromium@1.5.122: resolution: {integrity: sha512-EML1wnwkY5MFh/xUnCvY8FrhUuKzdYhowuZExZOfwJo+Zu9OsNCI23Cgl5y7awy7HrUHSwB1Z8pZX5TI34lsUg==} @@ -1856,6 +1870,10 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -1934,6 +1952,11 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob@11.0.2: + resolution: {integrity: sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==} + engines: {node: 20 || >=22} + hasBin: true + globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} @@ -2252,6 +2275,10 @@ packages: resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} engines: {node: '>= 0.4'} + jackspeak@4.1.0: + resolution: {integrity: sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==} + engines: {node: 20 || >=22} + jiti@2.4.2: resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} hasBin: true @@ -2422,6 +2449,10 @@ packages: resolution: {integrity: sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ==} engines: {node: '>=0.10.0'} + lru-cache@11.1.0: + resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -2632,6 +2663,10 @@ packages: resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} hasBin: true + minimatch@10.0.1: + resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==} + engines: {node: 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -2646,6 +2681,10 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + mkdirp@2.1.6: resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==} engines: {node: '>=10'} @@ -2811,6 +2850,9 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -2854,6 +2896,10 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-scurry@2.0.0: + resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} + engines: {node: 20 || >=22} + path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} @@ -3243,6 +3289,10 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + string.prototype.includes@2.0.1: resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} engines: {node: '>= 0.4'} @@ -3564,6 +3614,10 @@ packages: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -3971,6 +4025,15 @@ snapshots: optionalDependencies: '@types/node': 20.17.24 + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + '@jridgewell/gen-mapping@0.3.8': dependencies: '@jridgewell/set-array': 1.2.1 @@ -4742,6 +4805,8 @@ snapshots: dependencies: color-convert: 2.0.1 + ansi-styles@6.2.1: {} + argparse@1.0.10: dependencies: sprintf-js: 1.0.3 @@ -5100,6 +5165,8 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + eastasianwidth@0.2.0: {} + electron-to-chromium@1.5.122: {} emoji-regex-xs@1.0.0: {} @@ -5551,6 +5618,11 @@ snapshots: dependencies: is-callable: 1.2.7 + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 @@ -5632,6 +5704,15 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@11.0.2: + dependencies: + foreground-child: 3.3.1 + jackspeak: 4.1.0 + minimatch: 10.0.1 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.0 + globals@11.12.0: {} globals@14.0.0: {} @@ -5999,6 +6080,10 @@ snapshots: has-symbols: 1.1.0 set-function-name: 2.0.2 + jackspeak@4.1.0: + dependencies: + '@isaacs/cliui': 8.0.2 + jiti@2.4.2: {} js-tokens@4.0.0: {} @@ -6141,6 +6226,8 @@ snapshots: currently-unhandled: 0.4.1 signal-exit: 3.0.7 + lru-cache@11.1.0: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -6628,6 +6715,10 @@ snapshots: mini-svg-data-uri@1.4.4: {} + minimatch@10.0.1: + dependencies: + brace-expansion: 2.0.1 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -6642,6 +6733,8 @@ snapshots: minimist@1.2.8: {} + minipass@7.1.2: {} + mkdirp@2.1.6: {} motion-dom@12.5.0: @@ -6849,6 +6942,8 @@ snapshots: dependencies: p-limit: 3.1.0 + package-json-from-dist@1.0.1: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -6894,6 +6989,11 @@ snapshots: path-parse@1.0.7: {} + path-scurry@2.0.0: + dependencies: + lru-cache: 11.1.0 + minipass: 7.1.2 + path-to-regexp@6.3.0: {} path-type@1.1.0: @@ -7436,6 +7536,12 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + string.prototype.includes@2.0.1: dependencies: call-bind: 1.0.8 @@ -7851,6 +7957,12 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + y18n@5.0.8: {} yallist@3.1.1: {} diff --git a/public/r/button.json b/public/r/button.json index 7096274..475467d 100644 --- a/public/r/button.json +++ b/public/r/button.json @@ -3,16 +3,15 @@ "name": "button", "type": "registry:component", "title": "Button", - "description": "A button component with multiple variants for React Native applications.", + "description": "A button component for React Native applications.", "dependencies": [ - "@radix-ui/react-slot", "class-variance-authority" ], "registryDependencies": [], "files": [ { "path": "registry/button/button.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 gap-2 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n {\n variants: {\n variant: {\n default:\n \"bg-primary text-primary-foreground shadow hover:bg-primary/90\",\n destructive:\n \"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90\",\n outline:\n \"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground\",\n secondary:\n \"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80\",\n ghost: \"hover:bg-accent hover:text-accent-foreground\",\n link: \"text-primary underline-offset-4 hover:underline\",\n },\n size: {\n default: \"h-9 px-4 py-2\",\n sm: \"h-8 rounded-md px-3 text-xs\",\n lg: \"h-10 rounded-md px-8\",\n icon: \"h-9 w-9\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n)\n\nexport interface ButtonProps\n extends React.ButtonHTMLAttributes,\n VariantProps {\n asChild?: boolean\n}\n\nconst Button = React.forwardRef(\n ({ className, variant, size, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\"\n return (\n \n )\n }\n)\nButton.displayName = \"Button\"\n\nexport { Button, buttonVariants } ", + "content": "import * as React from \"react\";\nimport {\n Pressable,\n PressableProps as RNPressableProps,\n View,\n ViewStyle,\n PressableStateCallbackType,\n} from \"react-native\";\nimport { cn } from \"@/lib/utils\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nexport const buttonVariants = cva(\n \"flex-row items-center justify-center rounded-md\",\n {\n variants: {\n variant: {\n default:\n \"bg-primary text-primary-foreground dark:bg-primary dark:text-primary-foreground shadow\",\n destructive:\n \"bg-destructive text-destructive-foreground dark:bg-destructive dark:text-destructive-foreground shadow-sm\",\n outline:\n \"border border-input bg-background text-foreground dark:border-input dark:bg-background dark:text-foreground shadow-sm\",\n secondary:\n \"bg-secondary text-secondary-foreground dark:bg-secondary dark:text-secondary-foreground shadow-sm\",\n ghost: \"text-foreground dark:text-foreground\",\n link: \"text-primary dark:text-primary underline\",\n },\n size: {\n default: \"h-12 px-6\",\n sm: \"h-10 px-4\",\n lg: \"h-14 px-8\",\n icon: \"h-12 w-12\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n);\n\nexport interface ButtonProps\n extends Omit,\n VariantProps {\n className?: string;\n style?: ViewStyle;\n asChild?: boolean;\n}\n\nconst Button = React.forwardRef(\n ({ className, variant, size, asChild = false, children, ...props }, ref) => {\n return (\n \n {(state: PressableStateCallbackType) => (\n \n {typeof children === \"function\" ? children(state) : children}\n \n )}\n \n );\n }\n);\n\nButton.displayName = \"Button\";\n\nexport { Button };\n", "type": "registry:component" } ] diff --git a/registry/button/button.tsx b/registry/button/button.tsx index e6012d5..daf8741 100644 --- a/registry/button/button.tsx +++ b/registry/button/button.tsx @@ -1,30 +1,36 @@ -"use client" +import * as React from "react"; +import { + Pressable, + PressableProps as RNPressableProps, + View, + ViewStyle, + PressableStateCallbackType, +} from "react-native"; +import { cn } from "@/lib/utils"; -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" +import { cva, type VariantProps } from "class-variance-authority"; -const buttonVariants = cva( - "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 gap-2 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", +export const buttonVariants = cva( + "flex-row items-center justify-center rounded-md", { variants: { variant: { default: - "bg-primary text-primary-foreground shadow hover:bg-primary/90", + "bg-primary text-primary-foreground dark:bg-primary dark:text-primary-foreground shadow", destructive: - "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", + "bg-destructive text-destructive-foreground dark:bg-destructive dark:text-destructive-foreground shadow-sm", outline: - "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", + "border border-input bg-background text-foreground dark:border-input dark:bg-background dark:text-foreground shadow-sm", secondary: - "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", - ghost: "hover:bg-accent hover:text-accent-foreground", - link: "text-primary underline-offset-4 hover:underline", + "bg-secondary text-secondary-foreground dark:bg-secondary dark:text-secondary-foreground shadow-sm", + ghost: "text-foreground dark:text-foreground", + link: "text-primary dark:text-primary underline", }, size: { - default: "h-9 px-4 py-2", - sm: "h-8 rounded-md px-3 text-xs", - lg: "h-10 rounded-md px-8", - icon: "h-9 w-9", + default: "h-12 px-6", + sm: "h-10 px-4", + lg: "h-14 px-8", + icon: "h-12 w-12", }, }, defaultVariants: { @@ -32,26 +38,38 @@ const buttonVariants = cva( size: "default", }, } -) +); export interface ButtonProps - extends React.ButtonHTMLAttributes, + extends Omit, VariantProps { - asChild?: boolean + className?: string; + style?: ViewStyle; + asChild?: boolean; } -const Button = React.forwardRef( - ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button" +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, children, ...props }, ref) => { return ( - - ) + > + {(state: PressableStateCallbackType) => ( + + {typeof children === "function" ? children(state) : children} + + )} +
+ ); } -) -Button.displayName = "Button" +); -export { Button, buttonVariants } \ No newline at end of file +Button.displayName = "Button"; + +export { Button }; diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..642f8ce --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,48 @@ +# Component Documentation System + +This directory contains scripts to automate the process of documenting React Native components for your Next.js site. + +## Available Scripts + +### `npm run update:components` + +The main script that performs the entire documentation process: +1. Builds the component registry JSON files from TSX components +2. Generates component documentation pages for the Next.js site + +This is the recommended script to run when you update or add new components. + +### Individual Scripts + +- `npm run build:registry`: Only builds the registry JSON files +- `npm run generate:docs`: Only generates the documentation pages + +## How It Works + +1. **Component Source**: Your React Native components in `registry/*/component.tsx` are the source of truth. + +2. **Build Registry**: The build script extracts metadata and code from your components and generates JSON files in `public/r/`. + +3. **Generate Docs**: The docs generator creates Next.js pages in `app/(site)/docs/components/*/page.tsx` based on the JSON files. + +## Adding Documentation to Components + +Use JSDoc comments in your component files to improve documentation: + +```tsx +/** + * A detailed description of your component. + * This will be extracted and used in the documentation. + */ +export const MyComponent = () => { + // ... +} +``` + +## Customizing Documentation + +You can customize the documentation generation process by modifying the scripts: + +- `scripts/build-registry.js`: Customize how component data is extracted and stored +- `scripts/generate-component-docs.js`: Customize how documentation pages are generated +- `scripts/update-components.js`: Customize the workflow for updating components \ No newline at end of file diff --git a/scripts/build-registry.js b/scripts/build-registry.js new file mode 100644 index 0000000..900c0d7 --- /dev/null +++ b/scripts/build-registry.js @@ -0,0 +1,76 @@ +const fs = require('fs'); +const path = require('path'); +const glob = require('glob'); + +// Configuration +const REGISTRY_DIR = path.join(process.cwd(), 'registry'); +const OUTPUT_DIR = path.join(process.cwd(), 'public/r'); +const REGISTRY_SCHEMA = 'https://ui.shadcn.com/schema/registry-item.json'; + +// Ensure output directory exists +if (!fs.existsSync(OUTPUT_DIR)) { + fs.mkdirSync(OUTPUT_DIR, { recursive: true }); +} + +// Get all component directories +const componentDirs = fs.readdirSync(REGISTRY_DIR, { withFileTypes: true }) + .filter(dirent => dirent.isDirectory()) + .map(dirent => dirent.name); + +// Process each component +componentDirs.forEach(componentName => { + const componentDir = path.join(REGISTRY_DIR, componentName); + const mainComponentFile = path.join(componentDir, `${componentName}.tsx`); + + if (!fs.existsSync(mainComponentFile)) { + console.warn(`Main component file not found for ${componentName}`); + return; + } + + // Read component file content + const componentContent = fs.readFileSync(mainComponentFile, 'utf8'); + + // Extract component description from JSDoc comments if present + let description = `A ${componentName} component for React Native applications.`; + const descriptionMatch = componentContent.match(/\/\*\*\s*\n\s*\*\s*(.*?)\s*\n/); + if (descriptionMatch && descriptionMatch[1]) { + description = descriptionMatch[1]; + } + + // Determine dependencies + const dependencies = []; + + // Check for common dependencies in the imports + if (componentContent.includes('class-variance-authority')) { + dependencies.push('class-variance-authority'); + } + if (componentContent.includes('@radix-ui/react-slot')) { + dependencies.push('@radix-ui/react-slot'); + } + + // Create registry item + const registryItem = { + "$schema": REGISTRY_SCHEMA, + "name": componentName, + "type": "registry:component", + "title": componentName.charAt(0).toUpperCase() + componentName.slice(1), + "description": description, + "dependencies": dependencies, + "registryDependencies": [], + "files": [ + { + "path": `registry/${componentName}/${componentName}.tsx`, + "content": componentContent, + "type": "registry:component" + } + ] + }; + + // Write to output file + const outputFile = path.join(OUTPUT_DIR, `${componentName}.json`); + fs.writeFileSync(outputFile, JSON.stringify(registryItem, null, 2)); + + console.log(`āœ… Generated registry item for ${componentName}`); +}); + +console.log(`\nšŸŽ‰ Registry build complete! Generated ${componentDirs.length} components.`); \ No newline at end of file diff --git a/scripts/generate-component-docs.js b/scripts/generate-component-docs.js new file mode 100644 index 0000000..60caf5a --- /dev/null +++ b/scripts/generate-component-docs.js @@ -0,0 +1,205 @@ +const fs = require('fs'); +const path = require('path'); + +// Configuration +const PUBLIC_REGISTRY_DIR = path.join(process.cwd(), 'public/r'); +const DOCS_COMPONENTS_DIR = path.join(process.cwd(), 'app/(site)/docs/components'); + +// Ensure docs components directory exists +if (!fs.existsSync(DOCS_COMPONENTS_DIR)) { + fs.mkdirSync(DOCS_COMPONENTS_DIR, { recursive: true }); +} + +// Get all registry JSON files +const registryFiles = fs.readdirSync(PUBLIC_REGISTRY_DIR) + .filter(file => file.endsWith('.json')); + +// Process each component +registryFiles.forEach(jsonFile => { + const componentName = path.basename(jsonFile, '.json'); + const jsonPath = path.join(PUBLIC_REGISTRY_DIR, jsonFile); + + // Read the component JSON + const componentJson = JSON.parse(fs.readFileSync(jsonPath, 'utf8')); + + // Create component directory if it doesn't exist + const componentDir = path.join(DOCS_COMPONENTS_DIR, componentName); + if (!fs.existsSync(componentDir)) { + fs.mkdirSync(componentDir, { recursive: true }); + } + + // Get component code from JSON + const componentCode = componentJson.files[0].content; + + // Parse component code to extract variants and sizes + const variants = extractVariants(componentCode); + const sizes = extractSizes(componentCode); + + // Generate examples based on variants and sizes + const examples = generateExamples(componentName, variants, sizes); + + // Generate page content + const pageContent = generatePageContent( + componentName, + componentJson.description, + examples, + componentCode + ); + + // Write the page file + const pagePath = path.join(componentDir, 'page.tsx'); + fs.writeFileSync(pagePath, pageContent); + + console.log(`āœ… Generated documentation page for ${componentName}`); +}); + +console.log(`\nšŸŽ‰ Documentation generation complete!`); + +/** + * Extract variants from component code + */ +function extractVariants(code) { + // Find the variants section inside buttonVariants (or similar) + const variantsMatch = code.match(/variant:\s*{([^}]*)}/s); + if (!variantsMatch) return ['default']; + + const variantsBlock = variantsMatch[1]; + + // Now extract each variant name defined in the component + const variantRegex = /\s+(\w+):\s*["|']/g; + const variantMatches = []; + let match; + + while ((match = variantRegex.exec(variantsBlock)) !== null) { + variantMatches.push(match[1]); + } + + return variantMatches.length > 0 ? variantMatches : ['default']; +} + +/** + * Extract sizes from component code + */ +function extractSizes(code) { + const sizesMatch = code.match(/size:\s*{([^}]*)}/s); + if (!sizesMatch) return ['default']; + + const sizesBlock = sizesMatch[1]; + // Extract only the size names (without the comments and values) + const sizeMatches = Array.from( + sizesBlock.matchAll(/\s+(\w+):\s*/g), + m => m[1] + ); + + // Return unique sizes + return [...new Set(sizeMatches)]; +} + +/** + * Generate examples based on component properties + */ +function generateExamples(componentName, variants, sizes) { + const formattedComponentName = componentName.charAt(0).toUpperCase() + componentName.slice(1); + + const examples = [ + { + title: "Default", + value: "default", + content: `import { ${formattedComponentName} } from "@nativeui/ui"; + +export default function ${formattedComponentName}Demo() { + return ( + <${formattedComponentName}> + Click me + + ); +}`, + language: "tsx", + } + ]; + + if (variants.length > 1) { + // Add an example showing each variant + examples.push({ + title: "Variants", + value: "variants", + content: `import { ${formattedComponentName} } from "@nativeui/ui"; + +export default function ${formattedComponentName}Variants() { + return ( +
+ ${variants.map(v => `<${formattedComponentName} variant="${v}">${v.charAt(0).toUpperCase() + v.slice(1)}`).join('\n ')} +
+ ); +}`, + language: "tsx", + }); + } + + if (sizes.length > 1) { + // Add an example showing each size + examples.push({ + title: "Sizes", + value: "sizes", + content: `import { ${formattedComponentName} } from "@nativeui/ui"; + +export default function ${formattedComponentName}Sizes() { + return ( +
+ ${sizes.map(s => `<${formattedComponentName} size="${s}">${s === 'icon' ? 'šŸ‘‹' : s.charAt(0).toUpperCase() + s.slice(1)}`).join('\n ')} +
+ ); +}`, + language: "tsx", + }); + } + + return examples; +} + +/** + * Generate page content for the component + */ +function generatePageContent(componentName, description, examples, componentCode) { + const formattedComponentName = componentName.charAt(0).toUpperCase() + componentName.slice(1); + + // Generate preview code + const previewCode = `import { ${formattedComponentName} } from "@nativeui/ui"; + +export default function ${formattedComponentName}Demo() { + return ( +
+ <${formattedComponentName}>Default ${formattedComponentName} + <${formattedComponentName} variant="destructive">Delete + <${formattedComponentName} variant="outline">Outline + <${formattedComponentName} variant="secondary">Secondary + <${formattedComponentName} variant="ghost">Ghost + <${formattedComponentName} variant="link">Link +
+ ); +}`; + + // Safely escape component code to avoid eval errors + const safeComponentCode = componentCode + // Remove dynamic expressions that might reference undefined variables + .replace(/\${([^}]*)}/g, '""') + // Ensure proper string escaping + .replace(/`/g, '\\`'); + + return `import { ComponentPreview } from "@/components/docs/component-preview"; + +export default function ${formattedComponentName}Page() { + return ( + + ); +} +`; +} diff --git a/scripts/update-components.js b/scripts/update-components.js new file mode 100644 index 0000000..6a763c0 --- /dev/null +++ b/scripts/update-components.js @@ -0,0 +1,25 @@ +const { execSync } = require('child_process'); +const path = require('path'); + +console.log('šŸ”„ Updating component registry and documentation...\n'); + +// Step 1: Build the registry JSON files +console.log('šŸ“¦ Building component registry...'); +try { + execSync('node scripts/build-registry.js', { stdio: 'inherit' }); +} catch (error) { + console.error('āŒ Failed to build component registry:', error); + process.exit(1); +} + +// Step 2: Generate documentation pages +console.log('\nšŸ“ Generating component documentation...'); +try { + execSync('node scripts/generate-component-docs.js', { stdio: 'inherit' }); +} catch (error) { + console.error('āŒ Failed to generate component documentation:', error); + process.exit(1); +} + +console.log('\n✨ Component update complete! The registry and documentation have been updated.'); +console.log(' You can now run "npm run dev" to see the changes in your site.'); \ No newline at end of file