Skip to content

Commit b564a6a

Browse files
committed
feat(form): add form components & variants, support type exports, improve input styles
1 parent 66572b2 commit b564a6a

File tree

6 files changed

+76
-5
lines changed

6 files changed

+76
-5
lines changed

packages/ui/package.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,19 @@
55
"private": false,
66
"exports": {
77
".": {
8-
"import": "./src/index.ts"
8+
"types": "./dist/index.d.ts",
9+
"import": "./dist/index.js",
10+
"require": "./dist/index.js"
11+
},
12+
"./*": {
13+
"types": "./dist/components/*/index.d.ts",
14+
"import": "./dist/components/*/index.js",
15+
"require": "./dist/components/*/index.js"
16+
},
17+
"./utils": {
18+
"types": "./dist/lib/index.d.ts",
19+
"import": "./dist/lib/index.js",
20+
"require": "./dist/lib/index.js"
921
}
1022
},
1123
"main": "./dist/index.js",

packages/ui/registry.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,33 @@
10861086
"title": "Dropdown Menu",
10871087
"type": "registry:block"
10881088
},
1089+
{
1090+
"files": [
1091+
{
1092+
"path": "src/components/form/FormField.tsx",
1093+
"target": "components/form/FormField.tsx",
1094+
"type": "registry:ui"
1095+
},
1096+
{
1097+
"path": "src/components/form/form-variants.ts",
1098+
"target": "components/form/form-variants.ts",
1099+
"type": "registry:ui"
1100+
},
1101+
{
1102+
"path": "src/components/form/index.ts",
1103+
"target": "components/form/index.ts",
1104+
"type": "registry:ui"
1105+
},
1106+
{
1107+
"path": "src/components/form/types.ts",
1108+
"target": "components/form/types.ts",
1109+
"type": "registry:ui"
1110+
}
1111+
],
1112+
"name": "form",
1113+
"title": "Form",
1114+
"type": "registry:block"
1115+
},
10891116
{
10901117
"files": [
10911118
{

playground/public/r/carousel.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@
2626
},
2727
{
2828
"path": "src/components/carousel/CarouselNext.tsx",
29-
"content": "'use client';\n\nimport { ChevronRight } from 'lucide-react';\nimport { forwardRef } from 'react';\n\nimport { cn } from '@/lib/utils';\n\nimport { Button } from '../button';\n\nimport { carouselVariants } from './carousel-variants';\nimport { useCarousel } from './context';\nimport type { CarouselNextProps } from './types';\n\nconst CarouselNext = forwardRef<HTMLButtonElement, CarouselNextProps>((props, ref) => {\n const { children, className, disabled, shape = 'circle', size, variant = 'pure', ...rest } = props;\n\n const { canScrollNext, orientation, scrollNext } = useCarousel();\n\n const { next } = carouselVariants({ orientation, size });\n\n const nextClassName = cn(next(), className);\n\n return (\n <Button\n className={nextClassName}\n disabled={!canScrollNext || disabled}\n ref={ref}\n shape={shape}\n size={size}\n variant={variant}\n onClick={scrollNext}\n {...rest}\n >\n {children || <ChevronRight />}\n </Button>\n );\n});\n\nCarouselNext.displayName = 'CarouselNext';\n\nexport default CarouselNext;\n",
29+
"content": "'use client';\n\nimport { ChevronRight } from 'lucide-react';\nimport { forwardRef } from 'react';\n\nimport { cn } from '@/lib/utils';\n\nimport { ButtonIcon } from '../button';\n\nimport { carouselVariants } from './carousel-variants';\nimport { useCarousel } from './context';\nimport type { CarouselNextProps } from './types';\n\nconst CarouselNext = forwardRef<HTMLButtonElement, CarouselNextProps>((props, ref) => {\n const { children, className, disabled, shape = 'circle', size, variant = 'pure', ...rest } = props;\n\n const { canScrollNext, orientation, scrollNext } = useCarousel();\n\n const { next } = carouselVariants({ orientation, size });\n\n const nextClassName = cn(next(), className);\n\n return (\n <ButtonIcon\n className={nextClassName}\n disabled={!canScrollNext || disabled}\n ref={ref}\n shape={shape}\n size={size}\n variant={variant}\n onClick={scrollNext}\n {...rest}\n >\n {children || <ChevronRight />}\n </ButtonIcon>\n );\n});\n\nCarouselNext.displayName = 'CarouselNext';\n\nexport default CarouselNext;\n",
3030
"type": "registry:ui",
3131
"target": "components/carousel/CarouselNext.tsx"
3232
},
3333
{
3434
"path": "src/components/carousel/CarouselPrevious.tsx",
35-
"content": "'use client';\n\nimport { ChevronLeft } from 'lucide-react';\nimport { forwardRef } from 'react';\n\nimport { cn } from '@/lib/utils';\n\nimport { Button } from '../button';\n\nimport { carouselVariants } from './carousel-variants';\nimport { useCarousel } from './context';\nimport type { CarouselNextProps } from './types';\n\nconst CarouselNext = forwardRef<HTMLButtonElement, CarouselNextProps>((props, ref) => {\n const { children, className, disabled, shape = 'circle', size, variant = 'pure', ...rest } = props;\n\n const { canScrollPrev, orientation, scrollPrev } = useCarousel();\n\n const { previous } = carouselVariants({ orientation, size });\n\n const previousClassName = cn(previous(), className);\n\n return (\n <Button\n className={previousClassName}\n disabled={!canScrollPrev || disabled}\n ref={ref}\n shape={shape}\n size={size}\n variant={variant}\n onClick={scrollPrev}\n {...rest}\n >\n {children || <ChevronLeft />}\n </Button>\n );\n});\n\nCarouselNext.displayName = 'CarouselNext';\n\nexport default CarouselNext;\n",
35+
"content": "'use client';\n\nimport { ChevronLeft } from 'lucide-react';\nimport { forwardRef } from 'react';\n\nimport { cn } from '@/lib/utils';\n\nimport { ButtonIcon } from '../button';\n\nimport { carouselVariants } from './carousel-variants';\nimport { useCarousel } from './context';\nimport type { CarouselNextProps } from './types';\n\nconst CarouselNext = forwardRef<HTMLButtonElement, CarouselNextProps>((props, ref) => {\n const { children, className, disabled, shape = 'circle', size, variant = 'pure', ...rest } = props;\n\n const { canScrollPrev, orientation, scrollPrev } = useCarousel();\n\n const { previous } = carouselVariants({ orientation, size });\n\n const previousClassName = cn(previous(), className);\n\n return (\n <ButtonIcon\n className={previousClassName}\n disabled={!canScrollPrev || disabled}\n ref={ref}\n shape={shape}\n size={size}\n variant={variant}\n onClick={scrollPrev}\n {...rest}\n >\n {children || <ChevronLeft />}\n </ButtonIcon>\n );\n});\n\nCarouselNext.displayName = 'CarouselNext';\n\nexport default CarouselNext;\n",
3636
"type": "registry:ui",
3737
"target": "components/carousel/CarouselPrevious.tsx"
3838
},

playground/public/r/form.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
3+
"name": "form",
4+
"type": "registry:block",
5+
"title": "Form",
6+
"files": [
7+
{
8+
"path": "src/components/form/FormField.tsx",
9+
"content": "'use client';\n\nimport { useId } from 'react';\nimport type { AllPathsKeys } from 'skyroc-form';\nimport { Field, useFieldError } from 'skyroc-form';\n\nimport { cn } from '@/lib/utils';\n\nimport FormLabel from '../label/Label';\n\nimport { formVariants } from './form-variants';\nimport type { FormFieldProps } from './types';\n\nconst FormField = <Values = any,>(props: FormFieldProps<Values>) => {\n const { children, className, description, label, name, size, ...rest } = props;\n\n const id = useId();\n\n const errors = useFieldError<Values, AllPathsKeys<Values>>(name);\n\n const hasError = errors.length > 0;\n\n const formItemId = `${id}-form-item`;\n const formDescriptionId = `${id}-form-item-description`;\n const formMessageId = `${id}-form-item-message`;\n\n const {\n description: descriptionCls,\n item,\n label: labelCls,\n message\n } = formVariants({ error: errors.length > 0, size });\n\n const mergedCls = {\n description: cn(descriptionCls(), className),\n item: cn(item(), className),\n label: cn(labelCls()),\n message: cn(message(), className)\n };\n\n return (\n <div className={mergedCls.item}>\n <FormLabel\n className={mergedCls.label}\n data-error={hasError}\n data-slot=\"form-label\"\n htmlFor={id}\n size={size}\n >\n {label}\n </FormLabel>\n\n <Field\n {...rest}\n aria-describedby={!hasError ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}\n aria-invalid={hasError}\n id={id}\n name={name}\n >\n {children}\n </Field>\n\n {description && (\n <p\n className={mergedCls.description}\n id={formDescriptionId}\n >\n {description}\n </p>\n )}\n\n {hasError && (\n <p\n className={mergedCls.message}\n id={formMessageId}\n >\n {errors[0]}\n </p>\n )}\n </div>\n );\n};\n\nexport default FormField;\n",
10+
"type": "registry:ui",
11+
"target": "components/form/FormField.tsx"
12+
},
13+
{
14+
"path": "src/components/form/form-variants.ts",
15+
"content": "import { tv } from 'tailwind-variants';\n\nexport const formVariants = tv({\n defaultVariants: {\n error: false,\n size: 'md'\n },\n slots: {\n description: `text-muted-foreground`,\n item: `form-item`,\n label: 'flex items-center data-[error=true]:text-destructive',\n message: `font-medium text-destructive`\n },\n variants: {\n error: {\n true: {\n message: `text-destructive`\n }\n },\n size: {\n '2xl': {\n item: 'text-xl space-y-3.5',\n label: 'gap-3.5'\n },\n lg: {\n item: 'text-base space-y-2.5',\n label: 'gap-2.5'\n },\n md: {\n item: 'text-sm space-y-2',\n label: 'gap-2'\n },\n sm: {\n item: 'text-xs space-y-1.75',\n label: 'gap-1.75'\n },\n xl: {\n item: 'text-lg space-y-3',\n label: 'gap-3'\n },\n xs: {\n item: 'text-2xs space-y-1.5',\n label: 'gap-1.5'\n }\n }\n }\n});\n\nexport type FormSlots = keyof typeof formVariants.slots;\n",
16+
"type": "registry:ui",
17+
"target": "components/form/form-variants.ts"
18+
},
19+
{
20+
"path": "src/components/form/index.ts",
21+
"content": "'use client';\n\nexport {\n ComputedField as FormComputedField,\n Form,\n List as FormList,\n useEffectField,\n useFieldError,\n useFieldState,\n useForm,\n useSelector,\n useUndoRedo,\n useWatch\n} from 'skyroc-form';\n\nexport type { Action as FormAction, FormInstance } from 'skyroc-form';\n\nexport { default as FormField } from './FormField';\n\nexport type { FormFieldProps } from './types';\n",
22+
"type": "registry:ui",
23+
"target": "components/form/index.ts"
24+
},
25+
{
26+
"path": "src/components/form/types.ts",
27+
"content": "import type { ReactNode } from 'react';\nimport type { FieldProps } from 'skyroc-form';\n\nimport type { BaseProps } from '@/types/other';\n\nexport type FormFieldProps<Values = any> = FieldProps<Values> &\n BaseProps<{\n description?: string;\n error?: string;\n label?: ReactNode;\n required?: boolean;\n }>;\n",
28+
"type": "registry:ui",
29+
"target": "components/form/types.ts"
30+
}
31+
]
32+
}

playground/public/r/input.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
},
1919
{
2020
"path": "src/components/input/input-variants.ts",
21-
"content": "import { tv } from 'tailwind-variants';\n\nexport const inputVariants = tv({\n base: [\n `flex w-full rounded-md border border-solid border-input bg-background`,\n `file:border-0 file:bg-transparent file:font-medium`,\n `focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:ring-primary`,\n `disabled:cursor-not-allowed disabled:opacity-50`\n ],\n defaultVariants: {\n size: 'md'\n },\n variants: {\n size: {\n '2xl': 'h-12 px-4 text-xl file:py-2',\n lg: 'h-9 px-3 text-base file:py-1.25',\n md: 'h-8 px-2.5 text-sm file:py-1.25',\n sm: 'h-7 px-2 text-xs file:py-1.25',\n xl: 'h-10 px-3.5 text-lg file:py-1.25',\n xs: 'h-6 px-1.5 text-2xs file:py-1.25'\n }\n }\n});\n",
21+
"content": "import { tv } from 'tailwind-variants';\n\nexport const inputVariants = tv({\n base: [\n `flex w-full rounded-md border border-solid border-input bg-background`,\n `file:border-0 file:bg-transparent file:font-medium`,\n 'placeholder:text-muted-foreground selection:bg-primary',\n `focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:ring-primary`,\n `disabled:cursor-not-allowed disabled:opacity-50`,\n `aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive`\n ],\n defaultVariants: {\n size: 'md'\n },\n variants: {\n size: {\n '2xl': 'h-12 px-4 text-xl file:py-2',\n lg: 'h-9 px-3 text-base file:py-1.25',\n md: 'h-8 px-2.5 text-sm file:py-1.25',\n sm: 'h-7 px-2 text-xs file:py-1.25',\n xl: 'h-10 px-3.5 text-lg file:py-1.25',\n xs: 'h-6 px-1.5 text-2xs file:py-1.25'\n }\n }\n});\n",
2222
"type": "registry:ui",
2323
"target": "components/input/input-variants.ts"
2424
},

playground/public/r/resizable.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
},
1313
{
1414
"path": "src/components/resizable/ResizablePanelGroup.tsx",
15-
"content": "import { PanelGroup } from 'react-resizable-panels';\n\nimport { cn } from '@/lib/utils';\n\nimport { resizableVariants } from './resizable-variants';\nimport type { ResizablePanelGroupProps } from './types';\n\nconst ResizablePanelGroup = (props: ResizablePanelGroupProps) => {\n const { className, size, ...rest } = props;\n\n const { panelGroup } = resizableVariants({ size });\n\n const mergedCls = cn(panelGroup(), className);\n\n return (\n <PanelGroup\n className={mergedCls}\n {...rest}\n />\n );\n};\n\nexport default ResizablePanelGroup;\n",
15+
"content": "'use client';\n\nimport { PanelGroup } from 'react-resizable-panels';\n\nimport { cn } from '@/lib/utils';\n\nimport { resizableVariants } from './resizable-variants';\nimport type { ResizablePanelGroupProps } from './types';\n\nconst ResizablePanelGroup = (props: ResizablePanelGroupProps) => {\n const { className, size, ...rest } = props;\n\n const { panelGroup } = resizableVariants({ size });\n\n const mergedCls = cn(panelGroup(), className);\n\n return (\n <PanelGroup\n className={mergedCls}\n {...rest}\n />\n );\n};\n\nexport default ResizablePanelGroup;\n",
1616
"type": "registry:ui",
1717
"target": "components/resizable/ResizablePanelGroup.tsx"
1818
},

0 commit comments

Comments
 (0)