@luanthnh/cntt-ui 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.storybook/globals.d.ts +1 -0
- package/.storybook/main.ts +29 -0
- package/.storybook/preview.ts +32 -0
- package/README.md +86 -0
- package/assets/fonts/Montserrat-Black.eot +0 -0
- package/assets/fonts/Montserrat-Black.ttf +0 -0
- package/assets/fonts/Montserrat-Black.woff +0 -0
- package/assets/fonts/Montserrat-Black.woff2 +0 -0
- package/assets/fonts/Montserrat-BlackItalic.eot +0 -0
- package/assets/fonts/Montserrat-BlackItalic.ttf +0 -0
- package/assets/fonts/Montserrat-BlackItalic.woff +0 -0
- package/assets/fonts/Montserrat-BlackItalic.woff2 +0 -0
- package/assets/fonts/Montserrat-Bold.eot +0 -0
- package/assets/fonts/Montserrat-Bold.ttf +0 -0
- package/assets/fonts/Montserrat-Bold.woff +0 -0
- package/assets/fonts/Montserrat-Bold.woff2 +0 -0
- package/assets/fonts/Montserrat-BoldItalic.eot +0 -0
- package/assets/fonts/Montserrat-BoldItalic.ttf +0 -0
- package/assets/fonts/Montserrat-BoldItalic.woff +0 -0
- package/assets/fonts/Montserrat-BoldItalic.woff2 +0 -0
- package/assets/fonts/Montserrat-ExtraBold.eot +0 -0
- package/assets/fonts/Montserrat-ExtraBold.ttf +0 -0
- package/assets/fonts/Montserrat-ExtraBold.woff +0 -0
- package/assets/fonts/Montserrat-ExtraBold.woff2 +0 -0
- package/assets/fonts/Montserrat-ExtraBoldItalic.eot +0 -0
- package/assets/fonts/Montserrat-ExtraBoldItalic.ttf +0 -0
- package/assets/fonts/Montserrat-ExtraBoldItalic.woff +0 -0
- package/assets/fonts/Montserrat-ExtraBoldItalic.woff2 +0 -0
- package/assets/fonts/Montserrat-ExtraLight.eot +0 -0
- package/assets/fonts/Montserrat-ExtraLight.ttf +0 -0
- package/assets/fonts/Montserrat-ExtraLight.woff +0 -0
- package/assets/fonts/Montserrat-ExtraLight.woff2 +0 -0
- package/assets/fonts/Montserrat-ExtraLightItalic.eot +0 -0
- package/assets/fonts/Montserrat-ExtraLightItalic.ttf +0 -0
- package/assets/fonts/Montserrat-ExtraLightItalic.woff +0 -0
- package/assets/fonts/Montserrat-ExtraLightItalic.woff2 +0 -0
- package/assets/fonts/Montserrat-Italic.eot +0 -0
- package/assets/fonts/Montserrat-Italic.ttf +0 -0
- package/assets/fonts/Montserrat-Italic.woff +0 -0
- package/assets/fonts/Montserrat-Italic.woff2 +0 -0
- package/assets/fonts/Montserrat-Light.eot +0 -0
- package/assets/fonts/Montserrat-Light.ttf +0 -0
- package/assets/fonts/Montserrat-Light.woff +0 -0
- package/assets/fonts/Montserrat-Light.woff2 +0 -0
- package/assets/fonts/Montserrat-LightItalic.eot +0 -0
- package/assets/fonts/Montserrat-LightItalic.ttf +0 -0
- package/assets/fonts/Montserrat-LightItalic.woff +0 -0
- package/assets/fonts/Montserrat-LightItalic.woff2 +0 -0
- package/assets/fonts/Montserrat-Medium.eot +0 -0
- package/assets/fonts/Montserrat-Medium.ttf +0 -0
- package/assets/fonts/Montserrat-Medium.woff +0 -0
- package/assets/fonts/Montserrat-Medium.woff2 +0 -0
- package/assets/fonts/Montserrat-MediumItalic.eot +0 -0
- package/assets/fonts/Montserrat-MediumItalic.ttf +0 -0
- package/assets/fonts/Montserrat-MediumItalic.woff +0 -0
- package/assets/fonts/Montserrat-MediumItalic.woff2 +0 -0
- package/assets/fonts/Montserrat-Regular.eot +0 -0
- package/assets/fonts/Montserrat-Regular.ttf +0 -0
- package/assets/fonts/Montserrat-Regular.woff +0 -0
- package/assets/fonts/Montserrat-Regular.woff2 +0 -0
- package/assets/fonts/Montserrat-SemiBold.eot +0 -0
- package/assets/fonts/Montserrat-SemiBold.ttf +0 -0
- package/assets/fonts/Montserrat-SemiBold.woff +0 -0
- package/assets/fonts/Montserrat-SemiBold.woff2 +0 -0
- package/assets/fonts/Montserrat-SemiBoldItalic.eot +0 -0
- package/assets/fonts/Montserrat-SemiBoldItalic.ttf +0 -0
- package/assets/fonts/Montserrat-SemiBoldItalic.woff +0 -0
- package/assets/fonts/Montserrat-SemiBoldItalic.woff2 +0 -0
- package/assets/fonts/Montserrat-Thin.eot +0 -0
- package/assets/fonts/Montserrat-Thin.ttf +0 -0
- package/assets/fonts/Montserrat-Thin.woff +0 -0
- package/assets/fonts/Montserrat-Thin.woff2 +0 -0
- package/assets/fonts/Montserrat-ThinItalic.eot +0 -0
- package/assets/fonts/Montserrat-ThinItalic.ttf +0 -0
- package/assets/fonts/Montserrat-ThinItalic.woff +0 -0
- package/assets/fonts/Montserrat-ThinItalic.woff2 +0 -0
- package/assets/fonts/Montserrat-Variable.eot +0 -0
- package/assets/fonts/Montserrat-Variable.ttf +0 -0
- package/assets/fonts/Montserrat-Variable.woff +0 -0
- package/assets/fonts/Montserrat-Variable.woff2 +0 -0
- package/assets/fonts/Montserrat-VariableItalic.eot +0 -0
- package/assets/fonts/Montserrat-VariableItalic.ttf +0 -0
- package/assets/fonts/Montserrat-VariableItalic.woff +0 -0
- package/assets/fonts/Montserrat-VariableItalic.woff2 +0 -0
- package/assets/icons/arrow-left.svg +1 -0
- package/assets/icons/file.svg +1 -0
- package/assets/icons/globe.svg +1 -0
- package/assets/icons/logo-line.svg +1 -0
- package/assets/icons/next.svg +1 -0
- package/assets/icons/panel-left-expand.svg +1 -0
- package/assets/icons/placeholder.svg +57 -0
- package/assets/icons/vercel.svg +1 -0
- package/assets/icons/window.svg +1 -0
- package/assets/lotties/error-404.json +19642 -0
- package/assets/lotties/error.json +2414 -0
- package/assets/lotties/loader.json +305 -0
- package/components/Welcome.mdx +74 -0
- package/components/lenis/index.tsx +48 -0
- package/components/motion/auto-height.tsx +56 -0
- package/components/motion/cursor.tsx +108 -0
- package/components/motion/highlight.tsx +605 -0
- package/components/motion/number-ticker.tsx +55 -0
- package/components/motion/slot.tsx +106 -0
- package/components/motion/waves.tsx +417 -0
- package/components/primitives/tabs.tsx +174 -0
- package/components/ui/Accordion/index.stories.tsx +39 -0
- package/components/ui/Accordion/index.tsx +170 -0
- package/components/ui/Alert/index.stories.tsx +39 -0
- package/components/ui/Alert/index.tsx +60 -0
- package/components/ui/AlertDialog/index.stories.tsx +47 -0
- package/components/ui/AlertDialog/index.tsx +172 -0
- package/components/ui/AspectRatio/index.stories.tsx +40 -0
- package/components/ui/AspectRatio/index.tsx +9 -0
- package/components/ui/Avatar/index.stories.tsx +39 -0
- package/components/ui/Avatar/index.tsx +44 -0
- package/components/ui/Badge/index.stories.tsx +64 -0
- package/components/ui/Badge/index.tsx +46 -0
- package/components/ui/Breadcrumb/index.stories.tsx +64 -0
- package/components/ui/Breadcrumb/index.tsx +102 -0
- package/components/ui/Button/index.stories.tsx +232 -0
- package/components/ui/Button/index.tsx +114 -0
- package/components/ui/Calendar/index.stories.tsx +20 -0
- package/components/ui/Calendar/index.tsx +149 -0
- package/components/ui/Card/index.stories.tsx +39 -0
- package/components/ui/Card/index.tsx +65 -0
- package/components/ui/Carousel/index.stories.tsx +37 -0
- package/components/ui/Carousel/index.tsx +242 -0
- package/components/ui/Chart/index.stories.tsx +53 -0
- package/components/ui/Chart/index.tsx +322 -0
- package/components/ui/Checkbox/index.stories.tsx +56 -0
- package/components/ui/Checkbox/index.tsx +167 -0
- package/components/ui/CircleProcess/index.stories.tsx +29 -0
- package/components/ui/CircleProcess/index.tsx +50 -0
- package/components/ui/Collapsible/index.stories.tsx +33 -0
- package/components/ui/Collapsible/index.tsx +124 -0
- package/components/ui/Command/index.stories.tsx +65 -0
- package/components/ui/Command/index.tsx +161 -0
- package/components/ui/Container/index.stories.tsx +22 -0
- package/components/ui/Container/index.tsx +30 -0
- package/components/ui/ContextMenu/index.stories.tsx +51 -0
- package/components/ui/ContextMenu/index.tsx +224 -0
- package/components/ui/Dialog/index.stories.tsx +44 -0
- package/components/ui/Dialog/index.tsx +156 -0
- package/components/ui/Drawer/index.stories.tsx +54 -0
- package/components/ui/Drawer/index.tsx +124 -0
- package/components/ui/DropdownMenu/index.stories.tsx +83 -0
- package/components/ui/DropdownMenu/index.tsx +231 -0
- package/components/ui/Dropzone/index.stories.tsx +18 -0
- package/components/ui/Dropzone/index.tsx +47 -0
- package/components/ui/Form/date-field.tsx +77 -0
- package/components/ui/Form/index.stories.tsx +67 -0
- package/components/ui/Form/index.tsx +188 -0
- package/components/ui/Form/select-field.tsx +55 -0
- package/components/ui/Form/text-area-field.tsx +37 -0
- package/components/ui/Form/text-field.tsx +72 -0
- package/components/ui/HStack/index.stories.tsx +48 -0
- package/components/ui/HStack/index.tsx +73 -0
- package/components/ui/HoverCard/index.stories.tsx +38 -0
- package/components/ui/HoverCard/index.tsx +38 -0
- package/components/ui/Icons/index.stories.tsx +27 -0
- package/components/ui/Icons/index.tsx +33 -0
- package/components/ui/ImageWithFallback/index.stories.tsx +32 -0
- package/components/ui/ImageWithFallback/index.tsx +34 -0
- package/components/ui/Input/index.stories.tsx +47 -0
- package/components/ui/Input/index.tsx +21 -0
- package/components/ui/InputOtp/index.stories.tsx +35 -0
- package/components/ui/InputOtp/index.tsx +70 -0
- package/components/ui/Label/index.stories.tsx +18 -0
- package/components/ui/Label/index.tsx +21 -0
- package/components/ui/Marquee/index.stories.tsx +71 -0
- package/components/ui/Marquee/index.tsx +65 -0
- package/components/ui/Menubar/index.stories.tsx +116 -0
- package/components/ui/Menubar/index.tsx +252 -0
- package/components/ui/NavigationMenu/index.stories.tsx +112 -0
- package/components/ui/NavigationMenu/index.tsx +185 -0
- package/components/ui/NoData/index.stories.tsx +24 -0
- package/components/ui/NoData/index.tsx +19 -0
- package/components/ui/Pagination/index.stories.tsx +53 -0
- package/components/ui/Pagination/index.tsx +114 -0
- package/components/ui/Popover/index.stories.tsx +31 -0
- package/components/ui/Popover/index.tsx +42 -0
- package/components/ui/Progress/index.stories.tsx +35 -0
- package/components/ui/Progress/index.tsx +28 -0
- package/components/ui/RadioGroup/index.stories.tsx +28 -0
- package/components/ui/RadioGroup/index.tsx +45 -0
- package/components/ui/Resizable/index.stories.tsx +44 -0
- package/components/ui/Resizable/index.tsx +54 -0
- package/components/ui/ScrollArea/index.stories.tsx +31 -0
- package/components/ui/ScrollArea/index.tsx +56 -0
- package/components/ui/Select/index.stories.tsx +64 -0
- package/components/ui/Select/index.tsx +170 -0
- package/components/ui/Separator/index.stories.tsx +31 -0
- package/components/ui/Separator/index.tsx +28 -0
- package/components/ui/Sheet/index.stories.tsx +45 -0
- package/components/ui/Sheet/index.tsx +130 -0
- package/components/ui/Sidebar/index.stories.tsx +82 -0
- package/components/ui/Sidebar/index.tsx +676 -0
- package/components/ui/Skeleton/index.stories.tsx +36 -0
- package/components/ui/Skeleton/index.tsx +13 -0
- package/components/ui/Slider/index.stories.tsx +48 -0
- package/components/ui/Slider/index.tsx +82 -0
- package/components/ui/Slot/index.stories.tsx +29 -0
- package/components/ui/Slot/index.tsx +106 -0
- package/components/ui/Sonner/index.stories.tsx +36 -0
- package/components/ui/Sonner/index.tsx +31 -0
- package/components/ui/Switch/index.stories.tsx +33 -0
- package/components/ui/Switch/index.tsx +28 -0
- package/components/ui/Table/index.stories.tsx +74 -0
- package/components/ui/Table/index.tsx +95 -0
- package/components/ui/Tabs/index.stories.tsx +38 -0
- package/components/ui/Tabs/index.tsx +78 -0
- package/components/ui/Text/index.stories.tsx +53 -0
- package/components/ui/Text/index.tsx +138 -0
- package/components/ui/Textarea/index.stories.tsx +25 -0
- package/components/ui/Textarea/index.tsx +18 -0
- package/components/ui/Toggle/index.stories.tsx +52 -0
- package/components/ui/Toggle/index.tsx +46 -0
- package/components/ui/ToggleGroup/index.stories.tsx +52 -0
- package/components/ui/ToggleGroup/index.tsx +69 -0
- package/components/ui/Tooltip/index.stories.tsx +29 -0
- package/components/ui/Tooltip/index.tsx +35 -0
- package/components/ui/VStack/index.stories.tsx +45 -0
- package/components/ui/VStack/index.tsx +69 -0
- package/components/ui/colors.stories.tsx +148 -0
- package/dist/arrow-left-46B4CAEY.svg +1 -0
- package/dist/file-4IXBJF4J.svg +1 -0
- package/dist/globe-KVAXBN2U.svg +1 -0
- package/dist/index.cjs +6001 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +693 -0
- package/dist/index.d.ts +693 -0
- package/dist/index.js +5714 -0
- package/dist/index.js.map +1 -0
- package/dist/logo-line-QLUD5DAV.svg +1 -0
- package/dist/next-HOXZBJQP.svg +1 -0
- package/dist/panel-left-expand-SIPFBG4J.svg +1 -0
- package/dist/placeholder-H3V4XYVI.svg +57 -0
- package/dist/vercel-KFYFHF3A.svg +1 -0
- package/dist/window-JNUL4Q2E.svg +1 -0
- package/eslint.config.js +10 -0
- package/globals.css +994 -0
- package/hooks/index.ts +3 -0
- package/hooks/use-auto-height.tsx +99 -0
- package/hooks/use-controlled-state.tsx +32 -0
- package/hooks/use-mobile.ts +19 -0
- package/index.ts +58 -0
- package/lib/get-strict-context.ts +15 -0
- package/lib/utils.ts +10 -0
- package/package.json +107 -0
- package/scripts/generate-exports.ts +32 -0
- package/tsconfig.json +12 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/tsup.config.ts +11 -0
- package/types/svg.d.ts +10 -0
- package/vercel.json +5 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
|
|
3
|
+
import { Badge } from './index';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Badge> = {
|
|
6
|
+
title: 'UI/Badge',
|
|
7
|
+
component: Badge,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
argTypes: {
|
|
10
|
+
variant: {
|
|
11
|
+
control: 'select',
|
|
12
|
+
options: [
|
|
13
|
+
'default',
|
|
14
|
+
'secondary',
|
|
15
|
+
'destructive',
|
|
16
|
+
'outline',
|
|
17
|
+
'success',
|
|
18
|
+
'success-solid',
|
|
19
|
+
'error',
|
|
20
|
+
'error-solid',
|
|
21
|
+
'warning',
|
|
22
|
+
'warning-solid',
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default meta;
|
|
29
|
+
type Story = StoryObj<typeof Badge>;
|
|
30
|
+
|
|
31
|
+
export const Default: Story = {
|
|
32
|
+
args: {
|
|
33
|
+
children: 'Badge',
|
|
34
|
+
variant: 'default',
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const Secondary: Story = {
|
|
39
|
+
args: {
|
|
40
|
+
children: 'Secondary',
|
|
41
|
+
variant: 'secondary',
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const Outline: Story = {
|
|
46
|
+
args: {
|
|
47
|
+
children: 'Outline',
|
|
48
|
+
variant: 'outline',
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const Success: Story = {
|
|
53
|
+
args: {
|
|
54
|
+
children: 'Success',
|
|
55
|
+
variant: 'success',
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const Error: Story = {
|
|
60
|
+
args: {
|
|
61
|
+
children: 'Error',
|
|
62
|
+
variant: 'error-solid',
|
|
63
|
+
},
|
|
64
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
3
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
4
|
+
|
|
5
|
+
import { cn } from '@/lib/utils';
|
|
6
|
+
|
|
7
|
+
const badgeVariants = cva(
|
|
8
|
+
'focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-md border px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] [&>svg]:pointer-events-none [&>svg]:size-3',
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default: 'bg-primary text-primary-foreground [a&]:hover:bg-primary/90 border-transparent',
|
|
13
|
+
secondary:
|
|
14
|
+
'bg-primary-50 dark:bg-primary-900/20 text-secondary-foreground [a&]:hover:bg-secondary/90 border-transparent',
|
|
15
|
+
destructive:
|
|
16
|
+
'bg-destructive [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 border-transparent text-white',
|
|
17
|
+
outline:
|
|
18
|
+
'bg-primary/10 text-primary inline-flex items-center rounded-full px-2 py-1 text-xs font-medium',
|
|
19
|
+
success: 'border-green-500 bg-green-50 text-green-500 [a&]:hover:bg-green-50/90',
|
|
20
|
+
'success-solid': 'border-transparent bg-green-500 text-white [a&]:hover:bg-green-500/90',
|
|
21
|
+
error: 'border-red-500 bg-red-50 text-red-500 [a&]:hover:bg-red-50/90',
|
|
22
|
+
'error-solid': 'border-transparent bg-red-500 text-white [a&]:hover:bg-red-500/90',
|
|
23
|
+
warning: 'border-yellow-500 bg-yellow-50 text-yellow-500 [a&]:hover:bg-yellow-50/90',
|
|
24
|
+
'warning-solid': 'border-transparent bg-yellow-500 text-white [a&]:hover:bg-yellow-500/90',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
defaultVariants: {
|
|
28
|
+
variant: 'default',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
function Badge({
|
|
34
|
+
className,
|
|
35
|
+
variant,
|
|
36
|
+
asChild = false,
|
|
37
|
+
...props
|
|
38
|
+
}: React.ComponentProps<'span'> & VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
|
39
|
+
const Comp = asChild ? Slot : 'span';
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Comp data-slot="badge" className={cn(badgeVariants({ variant }), className)} {...props} />
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { Badge, badgeVariants };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Breadcrumb,
|
|
5
|
+
BreadcrumbEllipsis,
|
|
6
|
+
BreadcrumbItem,
|
|
7
|
+
BreadcrumbLink,
|
|
8
|
+
BreadcrumbList,
|
|
9
|
+
BreadcrumbPage,
|
|
10
|
+
BreadcrumbSeparator,
|
|
11
|
+
} from './index';
|
|
12
|
+
|
|
13
|
+
const meta: Meta<typeof Breadcrumb> = {
|
|
14
|
+
title: 'UI/Breadcrumb',
|
|
15
|
+
component: Breadcrumb,
|
|
16
|
+
tags: ['autodocs'],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default meta;
|
|
20
|
+
type Story = StoryObj<typeof Breadcrumb>;
|
|
21
|
+
|
|
22
|
+
export const Default: Story = {
|
|
23
|
+
render: (args) => (
|
|
24
|
+
<Breadcrumb {...args}>
|
|
25
|
+
<BreadcrumbList>
|
|
26
|
+
<BreadcrumbItem>
|
|
27
|
+
<BreadcrumbLink href="/">Home</BreadcrumbLink>
|
|
28
|
+
</BreadcrumbItem>
|
|
29
|
+
<BreadcrumbSeparator />
|
|
30
|
+
<BreadcrumbItem>
|
|
31
|
+
<BreadcrumbLink href="/components">Components</BreadcrumbLink>
|
|
32
|
+
</BreadcrumbItem>
|
|
33
|
+
<BreadcrumbSeparator />
|
|
34
|
+
<BreadcrumbItem>
|
|
35
|
+
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
|
|
36
|
+
</BreadcrumbItem>
|
|
37
|
+
</BreadcrumbList>
|
|
38
|
+
</Breadcrumb>
|
|
39
|
+
),
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const WithEllipsis: Story = {
|
|
43
|
+
render: (args) => (
|
|
44
|
+
<Breadcrumb {...args}>
|
|
45
|
+
<BreadcrumbList>
|
|
46
|
+
<BreadcrumbItem>
|
|
47
|
+
<BreadcrumbLink href="/">Home</BreadcrumbLink>
|
|
48
|
+
</BreadcrumbItem>
|
|
49
|
+
<BreadcrumbSeparator />
|
|
50
|
+
<BreadcrumbItem>
|
|
51
|
+
<BreadcrumbEllipsis />
|
|
52
|
+
</BreadcrumbItem>
|
|
53
|
+
<BreadcrumbSeparator />
|
|
54
|
+
<BreadcrumbItem>
|
|
55
|
+
<BreadcrumbLink href="/components">Components</BreadcrumbLink>
|
|
56
|
+
</BreadcrumbItem>
|
|
57
|
+
<BreadcrumbSeparator />
|
|
58
|
+
<BreadcrumbItem>
|
|
59
|
+
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
|
|
60
|
+
</BreadcrumbItem>
|
|
61
|
+
</BreadcrumbList>
|
|
62
|
+
</Breadcrumb>
|
|
63
|
+
),
|
|
64
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
3
|
+
import { ChevronRight, MoreHorizontal } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
import { cn } from '@/lib/utils';
|
|
6
|
+
|
|
7
|
+
function Breadcrumb({ ...props }: React.ComponentProps<'nav'>) {
|
|
8
|
+
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function BreadcrumbList({ className, ...props }: React.ComponentProps<'ol'>) {
|
|
12
|
+
return (
|
|
13
|
+
<ol
|
|
14
|
+
data-slot="breadcrumb-list"
|
|
15
|
+
className={cn(
|
|
16
|
+
'text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5',
|
|
17
|
+
className,
|
|
18
|
+
)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function BreadcrumbItem({ className, ...props }: React.ComponentProps<'li'>) {
|
|
25
|
+
return (
|
|
26
|
+
<li
|
|
27
|
+
data-slot="breadcrumb-item"
|
|
28
|
+
className={cn('inline-flex items-center gap-1.5', className)}
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function BreadcrumbLink({
|
|
35
|
+
asChild,
|
|
36
|
+
className,
|
|
37
|
+
...props
|
|
38
|
+
}: React.ComponentProps<'a'> & {
|
|
39
|
+
asChild?: boolean;
|
|
40
|
+
}) {
|
|
41
|
+
const Comp = asChild ? Slot : 'a';
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Comp
|
|
45
|
+
data-slot="breadcrumb-link"
|
|
46
|
+
className={cn('hover:text-foreground transition-colors', className)}
|
|
47
|
+
{...props}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function BreadcrumbPage({ className, ...props }: React.ComponentProps<'span'>) {
|
|
53
|
+
return (
|
|
54
|
+
<span
|
|
55
|
+
data-slot="breadcrumb-page"
|
|
56
|
+
role="link"
|
|
57
|
+
aria-disabled="true"
|
|
58
|
+
aria-current="page"
|
|
59
|
+
className={cn('text-foreground font-normal', className)}
|
|
60
|
+
{...props}
|
|
61
|
+
/>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function BreadcrumbSeparator({ children, className, ...props }: React.ComponentProps<'li'>) {
|
|
66
|
+
return (
|
|
67
|
+
<li
|
|
68
|
+
data-slot="breadcrumb-separator"
|
|
69
|
+
role="presentation"
|
|
70
|
+
aria-hidden="true"
|
|
71
|
+
className={cn('[&>svg]:size-3.5', className)}
|
|
72
|
+
{...props}
|
|
73
|
+
>
|
|
74
|
+
{children ?? <ChevronRight />}
|
|
75
|
+
</li>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function BreadcrumbEllipsis({ className, ...props }: React.ComponentProps<'span'>) {
|
|
80
|
+
return (
|
|
81
|
+
<span
|
|
82
|
+
data-slot="breadcrumb-ellipsis"
|
|
83
|
+
role="presentation"
|
|
84
|
+
aria-hidden="true"
|
|
85
|
+
className={cn('flex size-9 items-center justify-center', className)}
|
|
86
|
+
{...props}
|
|
87
|
+
>
|
|
88
|
+
<MoreHorizontal className="size-4" />
|
|
89
|
+
<span className="sr-only">More</span>
|
|
90
|
+
</span>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export {
|
|
95
|
+
Breadcrumb,
|
|
96
|
+
BreadcrumbList,
|
|
97
|
+
BreadcrumbItem,
|
|
98
|
+
BreadcrumbLink,
|
|
99
|
+
BreadcrumbPage,
|
|
100
|
+
BreadcrumbSeparator,
|
|
101
|
+
BreadcrumbEllipsis,
|
|
102
|
+
};
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Check, Mail, Plus, Search, Settings, User } from 'lucide-react';
|
|
3
|
+
|
|
4
|
+
import { Button } from './index';
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof Button> = {
|
|
7
|
+
title: 'UI/Button',
|
|
8
|
+
component: Button,
|
|
9
|
+
tags: ['autodocs'],
|
|
10
|
+
parameters: {
|
|
11
|
+
componentSubtitle: 'A customizable button component with multiple variants and sizes',
|
|
12
|
+
docs: {
|
|
13
|
+
description: {
|
|
14
|
+
component:
|
|
15
|
+
'The Button component supports various variants, sizes, and states to match your design system.',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
argTypes: {
|
|
20
|
+
variant: {
|
|
21
|
+
control: 'select',
|
|
22
|
+
options: ['default', 'destructive', 'outline', 'outline-gray', 'secondary', 'ghost', 'link'],
|
|
23
|
+
description: 'The visual style of the button',
|
|
24
|
+
table: {
|
|
25
|
+
type: { summary: 'string' },
|
|
26
|
+
defaultValue: { summary: 'default' },
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
size: {
|
|
30
|
+
control: 'select',
|
|
31
|
+
options: ['sm', 'md', 'default', 'lg', 'icon-sm', 'icon', 'icon-lg'],
|
|
32
|
+
description: 'The size of the button',
|
|
33
|
+
table: {
|
|
34
|
+
type: { summary: 'string' },
|
|
35
|
+
defaultValue: { summary: 'md' },
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
disabled: {
|
|
39
|
+
control: 'boolean',
|
|
40
|
+
description: 'Disable the button',
|
|
41
|
+
table: {
|
|
42
|
+
type: { summary: 'boolean' },
|
|
43
|
+
defaultValue: { summary: 'false' },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
loading: {
|
|
47
|
+
control: 'boolean',
|
|
48
|
+
description: 'Show a loading spinner',
|
|
49
|
+
table: {
|
|
50
|
+
type: { summary: 'boolean' },
|
|
51
|
+
defaultValue: { summary: 'false' },
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
asChild: {
|
|
55
|
+
control: 'boolean',
|
|
56
|
+
description: 'Render as a different component',
|
|
57
|
+
table: {
|
|
58
|
+
type: { summary: 'boolean' },
|
|
59
|
+
defaultValue: { summary: 'false' },
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
className: {
|
|
63
|
+
control: 'text',
|
|
64
|
+
description: 'Additional CSS classes',
|
|
65
|
+
table: {
|
|
66
|
+
type: { summary: 'string' },
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
args: {
|
|
71
|
+
children: 'Button',
|
|
72
|
+
variant: 'default',
|
|
73
|
+
size: 'md',
|
|
74
|
+
disabled: false,
|
|
75
|
+
loading: false,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export default meta;
|
|
80
|
+
type Story = StoryObj<typeof Button>;
|
|
81
|
+
|
|
82
|
+
// Size Variants
|
|
83
|
+
export const Sizes: Story = {
|
|
84
|
+
render: () => (
|
|
85
|
+
<div className="space-y-4">
|
|
86
|
+
<div className="flex flex-wrap items-center gap-4">
|
|
87
|
+
<Button size="sm">Small</Button>
|
|
88
|
+
<Button size="md">Medium</Button>
|
|
89
|
+
<Button size="default">Default</Button>
|
|
90
|
+
<Button size="lg">Large</Button>
|
|
91
|
+
</div>
|
|
92
|
+
<div className="border-t pt-4">
|
|
93
|
+
<h3 className="mb-2 text-sm font-medium">Icon Buttons</h3>
|
|
94
|
+
<div className="flex items-center gap-4">
|
|
95
|
+
<Button size="icon-sm" aria-label="Settings">
|
|
96
|
+
<Settings />
|
|
97
|
+
</Button>
|
|
98
|
+
<Button size="icon" aria-label="User">
|
|
99
|
+
<User />
|
|
100
|
+
</Button>
|
|
101
|
+
<Button size="icon-lg" aria-label="Search">
|
|
102
|
+
<Search />
|
|
103
|
+
</Button>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
),
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Define the variant types
|
|
111
|
+
type ButtonVariant =
|
|
112
|
+
| 'default'
|
|
113
|
+
| 'destructive'
|
|
114
|
+
| 'outline'
|
|
115
|
+
| 'outline-gray'
|
|
116
|
+
| 'secondary'
|
|
117
|
+
| 'ghost'
|
|
118
|
+
| 'link';
|
|
119
|
+
|
|
120
|
+
// Variant Showcase
|
|
121
|
+
export const Variants: Story = {
|
|
122
|
+
render: () => {
|
|
123
|
+
const variants: ButtonVariant[] = [
|
|
124
|
+
'default',
|
|
125
|
+
'destructive',
|
|
126
|
+
'outline',
|
|
127
|
+
'outline-gray',
|
|
128
|
+
'secondary',
|
|
129
|
+
'ghost',
|
|
130
|
+
'link',
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<div className="space-y-6">
|
|
135
|
+
{variants.map((variant) => (
|
|
136
|
+
<div key={variant} className="space-y-2">
|
|
137
|
+
<h3 className="text-sm font-medium capitalize">{variant}</h3>
|
|
138
|
+
<div className="flex items-center gap-4">
|
|
139
|
+
<Button variant={variant} size="sm">
|
|
140
|
+
{variant === 'link' ? 'Link' : 'Button'}
|
|
141
|
+
</Button>
|
|
142
|
+
<Button variant={variant} size="md">
|
|
143
|
+
{variant === 'link' ? 'Link' : 'Button'}
|
|
144
|
+
</Button>
|
|
145
|
+
<Button variant={variant} size="icon" aria-label="Action">
|
|
146
|
+
<Plus />
|
|
147
|
+
</Button>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
))}
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
Variants.storyName = 'Variants';
|
|
157
|
+
|
|
158
|
+
// With Icons
|
|
159
|
+
export const WithIcons: Story = {
|
|
160
|
+
render: () => (
|
|
161
|
+
<div className="space-y-4">
|
|
162
|
+
<div className="flex items-center gap-4">
|
|
163
|
+
<Button>
|
|
164
|
+
<Mail className="mr-2" />
|
|
165
|
+
Send Email
|
|
166
|
+
</Button>
|
|
167
|
+
<Button variant="outline">
|
|
168
|
+
<Check className="mr-2" />
|
|
169
|
+
Confirm
|
|
170
|
+
</Button>
|
|
171
|
+
<Button variant="ghost">
|
|
172
|
+
<Settings className="mr-2" />
|
|
173
|
+
Settings
|
|
174
|
+
</Button>
|
|
175
|
+
</div>
|
|
176
|
+
<div className="flex items-center gap-4">
|
|
177
|
+
<Button size="sm">
|
|
178
|
+
<Mail className="mr-2" />
|
|
179
|
+
Small
|
|
180
|
+
</Button>
|
|
181
|
+
<Button size="md">
|
|
182
|
+
<Mail className="mr-2" />
|
|
183
|
+
Medium
|
|
184
|
+
</Button>
|
|
185
|
+
<Button size="lg">
|
|
186
|
+
<Mail className="mr-2" />
|
|
187
|
+
Large
|
|
188
|
+
</Button>
|
|
189
|
+
</div>
|
|
190
|
+
<Button variant="outline">
|
|
191
|
+
<Check className="mr-2" />
|
|
192
|
+
Confirm
|
|
193
|
+
</Button>
|
|
194
|
+
<Button variant="ghost">
|
|
195
|
+
<Settings className="mr-2" />
|
|
196
|
+
Settings
|
|
197
|
+
</Button>
|
|
198
|
+
</div>
|
|
199
|
+
),
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// States
|
|
203
|
+
export const States: Story = {
|
|
204
|
+
storyName: 'States',
|
|
205
|
+
render: () => (
|
|
206
|
+
<div className="space-y-4">
|
|
207
|
+
<div className="flex items-center gap-4">
|
|
208
|
+
<Button loading>Loading</Button>
|
|
209
|
+
<Button disabled>Disabled</Button>
|
|
210
|
+
</div>
|
|
211
|
+
<div className="flex items-center gap-4">
|
|
212
|
+
<Button variant="outline" loading>
|
|
213
|
+
<Mail className="mr-2" />
|
|
214
|
+
Sending...
|
|
215
|
+
</Button>
|
|
216
|
+
<Button variant="ghost" loading>
|
|
217
|
+
<Settings className="mr-2 animate-spin" />
|
|
218
|
+
Processing
|
|
219
|
+
</Button>
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
),
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// Playground
|
|
226
|
+
export const Playground: Story = {
|
|
227
|
+
storyName: 'Playground',
|
|
228
|
+
args: {
|
|
229
|
+
children: 'Click me',
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
Playground.storyName = 'Playground';
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
3
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
4
|
+
import { Loader2 } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
import { cn } from '@/lib/utils';
|
|
7
|
+
|
|
8
|
+
const buttonVariants = cva(
|
|
9
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 cursor-pointer items-center justify-center gap-1 rounded-md text-base font-medium whitespace-nowrap transition-all outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
10
|
+
{
|
|
11
|
+
variants: {
|
|
12
|
+
variant: {
|
|
13
|
+
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
|
14
|
+
destructive:
|
|
15
|
+
'bg-destructive hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white',
|
|
16
|
+
outline:
|
|
17
|
+
'text-primary border-primary hover:bg-primary/10 dark:border-primary dark:text-primary dark:hover:bg-primary/10 border dark:bg-transparent',
|
|
18
|
+
'outline-gray':
|
|
19
|
+
'dark:bg-input/30 dark:hover:bg-input/5 dark:hover:text-primary border border-gray-300 text-black hover:bg-gray-500/10 dark:border-gray-700 dark:text-gray-100 dark:hover:bg-gray-700/20',
|
|
20
|
+
secondary:
|
|
21
|
+
'bg-primary-100 text-secondary-foreground hover:bg-secondary/80 dark:text-primary dark:bg-white',
|
|
22
|
+
ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
|
|
23
|
+
link: 'text-primary underline-offset-4 hover:underline',
|
|
24
|
+
},
|
|
25
|
+
size: {
|
|
26
|
+
sm: 'h-8 text-sm px-3 rounded-md [&>svg]:size-3.5',
|
|
27
|
+
md: 'h-10 text-sm px-4 rounded-md [&>svg]:size-4',
|
|
28
|
+
default: 'h-12 text-base px-5 rounded-lg [&>svg]:size-5',
|
|
29
|
+
lg: 'h-14 text-base px-6 rounded-lg [&>svg]:size-6',
|
|
30
|
+
icon: 'size-9 rounded-lg [&>svg]:size-4',
|
|
31
|
+
'icon-sm': 'size-8 rounded-lg [&>svg]:size-3.5',
|
|
32
|
+
'icon-lg': 'size-10 rounded-lg [&>svg]:size-5',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
defaultVariants: {
|
|
36
|
+
variant: 'default',
|
|
37
|
+
size: 'md',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
type ButtonProps = React.ComponentProps<'button'> &
|
|
43
|
+
VariantProps<typeof buttonVariants> & {
|
|
44
|
+
asChild?: boolean;
|
|
45
|
+
disabled?: boolean;
|
|
46
|
+
loading?: boolean;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
50
|
+
(
|
|
51
|
+
{
|
|
52
|
+
className,
|
|
53
|
+
variant,
|
|
54
|
+
size,
|
|
55
|
+
asChild = false,
|
|
56
|
+
disabled = false,
|
|
57
|
+
loading = false,
|
|
58
|
+
children,
|
|
59
|
+
...props
|
|
60
|
+
},
|
|
61
|
+
ref,
|
|
62
|
+
) => {
|
|
63
|
+
const Comp = asChild ? Slot : 'button';
|
|
64
|
+
const isDisabled = disabled || loading;
|
|
65
|
+
|
|
66
|
+
// When loading, only show the spinner
|
|
67
|
+
const content = loading ? (
|
|
68
|
+
<Loader2
|
|
69
|
+
className="animate-spin"
|
|
70
|
+
style={{
|
|
71
|
+
width: '1em',
|
|
72
|
+
height: '1em',
|
|
73
|
+
fontSize: '1em',
|
|
74
|
+
}}
|
|
75
|
+
aria-hidden="true"
|
|
76
|
+
/>
|
|
77
|
+
) : (
|
|
78
|
+
children
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const buttonProps = {
|
|
82
|
+
'data-slot': 'button',
|
|
83
|
+
className: cn(
|
|
84
|
+
buttonVariants({
|
|
85
|
+
variant,
|
|
86
|
+
size,
|
|
87
|
+
className,
|
|
88
|
+
}),
|
|
89
|
+
),
|
|
90
|
+
disabled: isDisabled,
|
|
91
|
+
'aria-disabled': isDisabled,
|
|
92
|
+
...(isDisabled && { 'aria-busy': loading }),
|
|
93
|
+
...props,
|
|
94
|
+
} as const;
|
|
95
|
+
|
|
96
|
+
if (asChild) {
|
|
97
|
+
return (
|
|
98
|
+
<Comp ref={ref} {...buttonProps}>
|
|
99
|
+
<span className="contents">{content}</span>
|
|
100
|
+
</Comp>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<Comp ref={ref} {...buttonProps}>
|
|
106
|
+
{content}
|
|
107
|
+
</Comp>
|
|
108
|
+
);
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
Button.displayName = 'Button';
|
|
113
|
+
|
|
114
|
+
export { Button, buttonVariants, type ButtonProps };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
|
|
3
|
+
import { Calendar } from './index';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Calendar> = {
|
|
6
|
+
title: 'UI/Calendar',
|
|
7
|
+
component: Calendar,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default meta;
|
|
12
|
+
type Story = StoryObj<typeof Calendar>;
|
|
13
|
+
|
|
14
|
+
export const Default: Story = {
|
|
15
|
+
args: {
|
|
16
|
+
mode: 'single',
|
|
17
|
+
selected: new Date(),
|
|
18
|
+
className: 'rounded-md border',
|
|
19
|
+
},
|
|
20
|
+
};
|