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