@boxcustodia/library 2.0.0-alpha.12 → 2.0.0-alpha.14
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/dist/index.cjs.js +1 -138
- package/dist/index.d.ts +1087 -720
- package/dist/index.es.js +7011 -56097
- package/dist/theme.css +1 -1
- package/package.json +34 -26
- package/src/__doc__/Examples.tsx +1 -1
- package/src/__doc__/Intro.mdx +3 -3
- package/src/__doc__/Tabs.mdx +112 -0
- package/src/__doc__/V2.mdx +1246 -0
- package/src/components/accordion/accordion.stories.tsx +143 -0
- package/src/components/accordion/accordion.tsx +135 -0
- package/src/components/accordion/index.ts +1 -0
- package/src/components/alert/alert.stories.tsx +24 -4
- package/src/components/alert/alert.tsx +17 -9
- package/src/components/alert-dialog/alert-dialog.stories.tsx +24 -0
- package/src/components/alert-dialog/alert-dialog.test.tsx +1 -1
- package/src/components/alert-dialog/alert-dialog.tsx +58 -10
- package/src/components/auto-complete/auto-complete.stories.tsx +616 -200
- package/src/components/auto-complete/auto-complete.tsx +420 -68
- package/src/components/auto-complete/index.ts +0 -1
- package/src/components/avatar/avatar.stories.tsx +162 -21
- package/src/components/avatar/avatar.tsx +79 -20
- package/src/components/button/button.stories.tsx +219 -294
- package/src/components/button/button.test.tsx +10 -17
- package/src/components/button/button.tsx +78 -19
- package/src/components/button/components/base-button.tsx +30 -53
- package/src/components/button/index.ts +0 -1
- package/src/components/calendar/calendar.stories.tsx +1 -1
- package/src/components/calendar/calendar.tsx +4 -4
- package/src/components/card/card.stories.tsx +141 -69
- package/src/components/card/card.tsx +155 -54
- package/src/components/center/center.stories.tsx +22 -39
- package/src/components/checkbox/checkbox.stories.tsx +25 -5
- package/src/components/checkbox/checkbox.tsx +76 -15
- package/src/components/checkbox-group/checkbox-group.stories.tsx +116 -28
- package/src/components/checkbox-group/checkbox-group.tsx +84 -3
- package/src/components/combobox/combobox.stories.tsx +33 -23
- package/src/components/combobox/combobox.tsx +99 -77
- package/src/components/date-picker/date-input.stories.tsx +14 -6
- package/src/components/date-picker/date-input.tsx +2 -2
- package/src/components/date-picker/date-picker.model.ts +13 -4
- package/src/components/date-picker/date-picker.stories.tsx +38 -12
- package/src/components/date-picker/date-picker.tsx +28 -14
- package/src/components/dialog/dialog.stories.tsx +18 -0
- package/src/components/dialog/dialog.test.tsx +1 -1
- package/src/components/dialog/dialog.tsx +51 -20
- package/src/components/divider/divider.stories.tsx +126 -51
- package/src/components/divider/divider.tsx +16 -16
- package/src/components/dropzone/dropzone.stories.tsx +71 -90
- package/src/components/dropzone/dropzone.tsx +383 -105
- package/src/components/dropzone/index.ts +0 -1
- package/src/components/empty/empty.stories.tsx +165 -0
- package/src/components/empty/empty.tsx +156 -0
- package/src/components/empty/index.ts +1 -0
- package/src/components/field/field.stories.tsx +227 -4
- package/src/components/field/field.tsx +77 -42
- package/src/components/form/form.stories.tsx +320 -197
- package/src/components/form/form.tsx +3 -23
- package/src/components/index.ts +2 -6
- package/src/components/input/input.stories.tsx +5 -5
- package/src/components/input/input.tsx +4 -4
- package/src/components/kbd/kbd.stories.tsx +1 -0
- package/src/components/label/label.stories.tsx +16 -0
- package/src/components/label/label.tsx +13 -2
- package/src/components/loader/loader.stories.tsx +7 -5
- package/src/components/loader/loader.tsx +8 -3
- package/src/components/menu/menu-primitives.tsx +207 -196
- package/src/components/menu/menu.stories.tsx +276 -146
- package/src/components/menu/menu.tsx +146 -54
- package/src/components/number-input/number-input.stories.tsx +27 -4
- package/src/components/number-input/number-input.test.tsx +2 -2
- package/src/components/number-input/number-input.tsx +31 -33
- package/src/components/otp/index.ts +1 -0
- package/src/components/otp/otp.stories.tsx +209 -0
- package/src/components/otp/otp.tsx +100 -0
- package/src/components/pagination/index.ts +1 -0
- package/src/components/pagination/pagination.model.ts +2 -0
- package/src/components/pagination/pagination.stories.tsx +154 -59
- package/src/components/pagination/pagination.test.tsx +122 -57
- package/src/components/pagination/pagination.tsx +575 -77
- package/src/components/password/password.stories.tsx +18 -3
- package/src/components/password/password.tsx +29 -9
- package/src/components/popover/popover.stories.tsx +26 -5
- package/src/components/popover/popover.tsx +15 -23
- package/src/components/progress/progress.stories.tsx +1 -0
- package/src/components/radio-group/index.ts +1 -0
- package/src/components/radio-group/radio-group.stories.tsx +251 -0
- package/src/components/radio-group/radio-group.tsx +212 -0
- package/src/components/scroll-area/scroll-area.stories.tsx +1 -0
- package/src/components/select/select.stories.tsx +118 -19
- package/src/components/select/select.tsx +67 -62
- package/src/components/skeleton/skeleton.stories.tsx +1 -0
- package/src/components/stack/stack.stories.tsx +179 -89
- package/src/components/stack/stack.tsx +2 -2
- package/src/components/stepper/index.ts +1 -1
- package/src/components/stepper/stepper.stories.tsx +767 -83
- package/src/components/stepper/stepper.test.tsx +18 -18
- package/src/components/stepper/stepper.tsx +554 -0
- package/src/components/switch/switch.stories.tsx +15 -1
- package/src/components/switch/switch.tsx +17 -4
- package/src/components/table/index.ts +0 -2
- package/src/components/table/table.stories.tsx +131 -18
- package/src/components/table/table.test.tsx +1 -1
- package/src/components/table/table.tsx +183 -77
- package/src/components/tabs/tabs.stories.tsx +373 -155
- package/src/components/tabs/tabs.test.tsx +12 -12
- package/src/components/tabs/tabs.tsx +72 -149
- package/src/components/tag/index.ts +0 -1
- package/src/components/tag/tag.stories.tsx +155 -120
- package/src/components/tag/tag.tsx +47 -95
- package/src/components/textarea/textarea.stories.tsx +8 -22
- package/src/components/textarea/textarea.tsx +17 -79
- package/src/components/timeline/timeline.stories.tsx +323 -42
- package/src/components/timeline/timeline.tsx +359 -132
- package/src/components/toast/toast.stories.tsx +1 -0
- package/src/components/tooltip/tooltip.tsx +11 -9
- package/src/components/tree/index.ts +0 -1
- package/src/components/tree/tree.stories.tsx +365 -408
- package/src/components/tree/tree.test.tsx +163 -0
- package/src/components/tree/tree.tsx +212 -36
- package/src/hooks/useAsync/__doc__/useAsync.stories.tsx +5 -5
- package/src/hooks/useClipboard/__doc__/useClipboard.stories.tsx +1 -3
- package/src/hooks/useDebounceCallback/__doc__/useDebouncedCallback.stories.tsx +6 -6
- package/src/hooks/useDocumentTitle/__doc__/useDocumentTitle.stories.tsx +1 -1
- package/src/hooks/useEventListener/__test__/useEventListener.test.tsx +1 -1
- package/src/hooks/useLocalStorage/__doc__/useLocalStorage.stories.tsx +1 -1
- package/src/hooks/usePagination/usePagination.tsx +36 -24
- package/src/styles/theme.css +1 -1
- package/src/utils/form.tsx +67 -37
- package/src/utils/index.ts +1 -1
- package/src/__doc__/Migration.mdx +0 -475
- package/src/components/auto-complete/auto-complete-primitives.tsx +0 -155
- package/src/components/background-image/background-image.stories.tsx +0 -21
- package/src/components/background-image/background-image.test.tsx +0 -29
- package/src/components/background-image/background-image.tsx +0 -23
- package/src/components/background-image/index.ts +0 -1
- package/src/components/button/button.variants.ts +0 -44
- package/src/components/button/components/loader-overlay.tsx +0 -21
- package/src/components/button/components/loading-icon.tsx +0 -47
- package/src/components/dropzone/upload-primitives.tsx +0 -310
- package/src/components/dropzone/use-dropzone.ts +0 -122
- package/src/components/empty-state/empty-state.stories.tsx +0 -56
- package/src/components/empty-state/empty-state.tsx +0 -39
- package/src/components/empty-state/index.ts +0 -1
- package/src/components/heading/heading.stories.tsx +0 -74
- package/src/components/heading/heading.tsx +0 -28
- package/src/components/heading/heading.variants.ts +0 -27
- package/src/components/heading/index.ts +0 -1
- package/src/components/kbd/kbd.variants.ts +0 -26
- package/src/components/menu/util/render-menu-item.tsx +0 -54
- package/src/components/multi-select/hooks/use-multi-select.ts +0 -66
- package/src/components/multi-select/index.ts +0 -1
- package/src/components/multi-select/multi-select.stories.tsx +0 -294
- package/src/components/multi-select/multi-select.tsx +0 -300
- package/src/components/multi-select/multi-select.variants.ts +0 -22
- package/src/components/pagination/components/pagination-option.tsx +0 -27
- package/src/components/show/index.ts +0 -1
- package/src/components/show/show.stories.tsx +0 -197
- package/src/components/show/show.test.tsx +0 -41
- package/src/components/show/show.tsx +0 -16
- package/src/components/stepper/Stepper.tsx +0 -190
- package/src/components/stepper/context/stepper-context.tsx +0 -11
- package/src/components/table/table-primitives.tsx +0 -122
- package/src/components/table/table.model.ts +0 -20
- package/src/components/table-pagination/index.ts +0 -2
- package/src/components/table-pagination/table-pagination.model.ts +0 -2
- package/src/components/table-pagination/table-pagination.stories.tsx +0 -23
- package/src/components/table-pagination/table-pagination.test.tsx +0 -32
- package/src/components/table-pagination/table-pagination.tsx +0 -108
- package/src/components/tabs/context/tabs-context.tsx +0 -14
- package/src/components/tag/tag.variants.ts +0 -31
- package/src/components/timeline/timeline-status.ts +0 -5
- package/src/components/tree/hooks/use-controllable-tree-state.ts +0 -80
- package/src/components/tree/tree-primitives.tsx +0 -126
|
@@ -1,31 +1,90 @@
|
|
|
1
|
-
import { type VariantProps } from "class-variance-authority";
|
|
2
|
-
import { ComponentProps } from "react";
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
import { type ComponentProps, cloneElement, type ReactElement } from "react";
|
|
3
3
|
import { cn } from "../../lib";
|
|
4
|
-
import { useThemeProps } from "../../providers/theme/useThemeProps";
|
|
5
|
-
import { buttonVariants } from "./button.variants";
|
|
6
4
|
import { BaseButton } from "./components/base-button";
|
|
7
5
|
|
|
6
|
+
const buttonVariants = cva(
|
|
7
|
+
[
|
|
8
|
+
"group/button relative inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding",
|
|
9
|
+
"text-sm font-medium whitespace-nowrap transition-all outline-none select-none",
|
|
10
|
+
"focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50",
|
|
11
|
+
"active:not-aria-[haspopup]:translate-y-px",
|
|
12
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
13
|
+
"aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
|
|
14
|
+
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
15
|
+
"[&[data-loading]>:not([data-loader])]:opacity-0",
|
|
16
|
+
"[&[data-loading]]:pointer-events-none",
|
|
17
|
+
],
|
|
18
|
+
{
|
|
19
|
+
variants: {
|
|
20
|
+
variant: {
|
|
21
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
22
|
+
outline:
|
|
23
|
+
"border-border hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:hover:bg-input/50",
|
|
24
|
+
secondary:
|
|
25
|
+
"bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
|
26
|
+
ghost:
|
|
27
|
+
"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
|
|
28
|
+
error: "bg-error text-error-foreground hover:bg-error/90",
|
|
29
|
+
success: "bg-success text-success-foreground hover:bg-success/90",
|
|
30
|
+
warning: "bg-warning text-warning-foreground hover:bg-warning/90",
|
|
31
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
32
|
+
},
|
|
33
|
+
size: {
|
|
34
|
+
default:
|
|
35
|
+
"h-8 [--btn-gap:0.375rem] px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
36
|
+
xs: "h-6 [--btn-gap:0.25rem] rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
37
|
+
sm: "h-7 [--btn-gap:0.25rem] rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
|
38
|
+
lg: "h-9 [--btn-gap:0.375rem] px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
39
|
+
icon: "size-8",
|
|
40
|
+
"icon-xs":
|
|
41
|
+
"size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
|
|
42
|
+
"icon-sm":
|
|
43
|
+
"size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
|
|
44
|
+
"icon-lg": "size-9",
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
defaultVariants: {
|
|
48
|
+
variant: "default",
|
|
49
|
+
size: "default",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
|
|
8
54
|
type BaseButtonProps = ComponentProps<typeof BaseButton>;
|
|
9
|
-
type Props = BaseButtonProps &
|
|
55
|
+
type Props = BaseButtonProps &
|
|
56
|
+
VariantProps<typeof buttonVariants> & {
|
|
57
|
+
icon?: ReactElement;
|
|
58
|
+
};
|
|
10
59
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
60
|
+
const Button = ({
|
|
61
|
+
variant,
|
|
62
|
+
size,
|
|
63
|
+
className,
|
|
64
|
+
icon,
|
|
65
|
+
children,
|
|
66
|
+
...rest
|
|
67
|
+
}: Props) => {
|
|
68
|
+
const iconPosition =
|
|
69
|
+
(icon?.props as { "data-icon"?: string } | undefined)?.["data-icon"] ??
|
|
70
|
+
"inline-start";
|
|
71
|
+
const iconEl = icon
|
|
72
|
+
? cloneElement(icon as ReactElement<{ "data-icon"?: string }>, {
|
|
73
|
+
"data-icon": iconPosition,
|
|
74
|
+
})
|
|
75
|
+
: null;
|
|
16
76
|
|
|
17
77
|
return (
|
|
18
78
|
<BaseButton
|
|
19
79
|
{...rest}
|
|
20
|
-
className={cn(
|
|
21
|
-
buttonVariants({
|
|
22
|
-
variant,
|
|
23
|
-
size,
|
|
24
|
-
shape,
|
|
25
|
-
}),
|
|
26
|
-
className,
|
|
27
|
-
)}
|
|
80
|
+
className={cn(buttonVariants({ variant, size }), className)}
|
|
28
81
|
data-variant={variant || "default"}
|
|
29
|
-
|
|
82
|
+
>
|
|
83
|
+
{iconEl && iconPosition !== "inline-end" && iconEl}
|
|
84
|
+
{children}
|
|
85
|
+
{iconEl && iconPosition === "inline-end" && iconEl}
|
|
86
|
+
</BaseButton>
|
|
30
87
|
);
|
|
31
88
|
};
|
|
89
|
+
|
|
90
|
+
export { Button, buttonVariants };
|
|
@@ -1,86 +1,63 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Button as ButtonPrimitive } from "@base-ui/react/button";
|
|
2
2
|
import { useControllableState } from "@radix-ui/react-use-controllable-state";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { LoadingIcon } from "./loading-icon";
|
|
3
|
+
import { LoaderCircle } from "lucide-react";
|
|
4
|
+
import { type ComponentProps } from "react";
|
|
5
|
+
import { cn } from "../../../lib";
|
|
7
6
|
|
|
8
|
-
interface Props extends ComponentProps<
|
|
9
|
-
asChild?: boolean;
|
|
7
|
+
interface Props extends ComponentProps<typeof ButtonPrimitive> {
|
|
10
8
|
loading?: boolean;
|
|
11
|
-
showLoader?: boolean;
|
|
12
|
-
icon?: ReactNode;
|
|
13
|
-
iconPosition?: "start" | "end";
|
|
14
|
-
loaderReplace?: true;
|
|
15
9
|
}
|
|
16
10
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
gap: "0.5rem",
|
|
21
|
-
};
|
|
11
|
+
type OnClickEvent = Parameters<
|
|
12
|
+
NonNullable<ComponentProps<typeof ButtonPrimitive>["onClick"]>
|
|
13
|
+
>[0];
|
|
22
14
|
|
|
23
15
|
export const BaseButton = ({
|
|
24
|
-
asChild = false,
|
|
25
16
|
onClick,
|
|
26
17
|
loading: prop,
|
|
27
|
-
showLoader = true,
|
|
28
|
-
icon,
|
|
29
|
-
iconPosition = "start",
|
|
30
|
-
loaderReplace,
|
|
31
18
|
style,
|
|
19
|
+
children,
|
|
32
20
|
...props
|
|
33
21
|
}: Props) => {
|
|
34
|
-
const Comp = asChild ? Slot : "button";
|
|
35
22
|
const [loading = false, setLoading] = useControllableState<boolean>({
|
|
36
23
|
prop,
|
|
37
24
|
defaultProp: false,
|
|
38
25
|
});
|
|
39
26
|
|
|
40
|
-
const handleClick = async (e:
|
|
27
|
+
const handleClick = async (e: OnClickEvent) => {
|
|
41
28
|
if (!onClick || loading) return;
|
|
42
29
|
|
|
43
30
|
const onClickResult = onClick(e) as unknown;
|
|
44
31
|
|
|
45
32
|
if (onClickResult instanceof Promise) {
|
|
46
|
-
|
|
33
|
+
setLoading(true);
|
|
47
34
|
await onClickResult.finally(() => setLoading(false));
|
|
48
35
|
}
|
|
49
36
|
};
|
|
50
37
|
|
|
51
|
-
// TODO: Refactorizar lógica de renderización de iconos
|
|
52
|
-
const renderIconWithLoader = (position: "start" | "end") => {
|
|
53
|
-
if (position !== iconPosition) return null;
|
|
54
|
-
const isLoading = loaderReplace ? false : loading;
|
|
55
|
-
|
|
56
|
-
return (
|
|
57
|
-
<>
|
|
58
|
-
<LoadingIcon loading={isLoading && !icon} animate={true} />
|
|
59
|
-
{icon && <LoadingIcon loading={isLoading} animate={false} />}
|
|
60
|
-
{icon && !isLoading && icon}
|
|
61
|
-
</>
|
|
62
|
-
);
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const isLoading = loading && showLoader;
|
|
66
|
-
const isReplaceMode = loaderReplace && isLoading;
|
|
67
|
-
|
|
68
38
|
return (
|
|
69
|
-
<
|
|
70
|
-
type="button"
|
|
39
|
+
<ButtonPrimitive
|
|
71
40
|
data-slot="button"
|
|
41
|
+
data-loading={loading || undefined}
|
|
42
|
+
aria-busy={loading || undefined}
|
|
72
43
|
{...props}
|
|
73
44
|
onClick={handleClick}
|
|
74
|
-
style={
|
|
75
|
-
...baseStyles,
|
|
76
|
-
position: isReplaceMode ? "relative" : undefined,
|
|
77
|
-
...style,
|
|
78
|
-
}}
|
|
45
|
+
style={style}
|
|
79
46
|
>
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
47
|
+
<span className="inline-flex items-center gap-(--btn-gap)">
|
|
48
|
+
{children}
|
|
49
|
+
</span>
|
|
50
|
+
<LoaderCircle
|
|
51
|
+
data-loader
|
|
52
|
+
data-testid="btn-loader"
|
|
53
|
+
className={cn(
|
|
54
|
+
"hidden animate-spin",
|
|
55
|
+
"group-data-[loading]/button:absolute",
|
|
56
|
+
"group-data-[loading]/button:block",
|
|
57
|
+
"group-data-[loading]/button:inset-0",
|
|
58
|
+
"group-data-[loading]/button:m-auto",
|
|
59
|
+
)}
|
|
60
|
+
/>
|
|
61
|
+
</ButtonPrimitive>
|
|
85
62
|
);
|
|
86
63
|
};
|
|
@@ -9,7 +9,7 @@ import { Calendar } from "./calendar";
|
|
|
9
9
|
* modos de selección `single`, `range` y `multiple`. Acepta todas las props de `DayPicker`.
|
|
10
10
|
*
|
|
11
11
|
* Por defecto usa `captionLayout="dropdown"` con el componente `Select` de la librería
|
|
12
|
-
* para navegar por mes y año. El rango navegable cubre 100 años hacia atrás y
|
|
12
|
+
* para navegar por mes y año. El rango navegable cubre 100 años hacia atrás y 100 hacia
|
|
13
13
|
* adelante desde el año actual. Para volver al caption de texto, pasá `captionLayout="label"`.
|
|
14
14
|
*
|
|
15
15
|
* Sobreescribí `components.Dropdown` para reemplazar el dropdown por defecto con cualquier
|
|
@@ -14,7 +14,7 @@ import { Select } from "../select/select";
|
|
|
14
14
|
|
|
15
15
|
const _year = new Date().getFullYear();
|
|
16
16
|
const DEFAULT_START_MONTH = new Date(_year - 100, 0);
|
|
17
|
-
const DEFAULT_END_MONTH = new Date(_year +
|
|
17
|
+
const DEFAULT_END_MONTH = new Date(_year + 100, 11);
|
|
18
18
|
|
|
19
19
|
type _Opt = { label: string; value: number };
|
|
20
20
|
|
|
@@ -33,10 +33,10 @@ function CalendarDropdown({
|
|
|
33
33
|
<Select
|
|
34
34
|
items={items}
|
|
35
35
|
value={selected}
|
|
36
|
-
className="py-1"
|
|
36
|
+
className="py-1 h-[32px]"
|
|
37
37
|
getLabel={(o) => o.label}
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
getId={(o) => String(o.value)}
|
|
39
|
+
onValueChange={(item) => {
|
|
40
40
|
if (!onChange || item === null) return;
|
|
41
41
|
onChange({
|
|
42
42
|
target: { value: String(item.value) },
|
|
@@ -1,116 +1,188 @@
|
|
|
1
1
|
import { faker } from "@faker-js/faker";
|
|
2
|
-
import { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
3
3
|
import { ComponentType } from "react";
|
|
4
4
|
import {
|
|
5
5
|
Button,
|
|
6
6
|
Card,
|
|
7
|
+
CardAction,
|
|
7
8
|
CardContent,
|
|
8
9
|
CardDescription,
|
|
9
10
|
CardFooter,
|
|
10
11
|
CardHeader,
|
|
12
|
+
CardRoot,
|
|
11
13
|
CardTitle,
|
|
12
14
|
} from "../../components";
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* -
|
|
21
|
-
* -
|
|
22
|
-
* -
|
|
23
|
-
*
|
|
24
|
-
* ### Estructura básica
|
|
25
|
-
* ```tsx
|
|
26
|
-
* <Card>
|
|
27
|
-
* <CardHeader>
|
|
28
|
-
* <CardTitle>Título del Card</CardTitle>
|
|
29
|
-
* <CardDescription>Descripción opcional</CardDescription>
|
|
30
|
-
* </CardHeader>
|
|
31
|
-
* <CardContent>
|
|
32
|
-
* Contenido principal
|
|
33
|
-
* </CardContent>
|
|
34
|
-
* <CardFooter>
|
|
35
|
-
* Acciones o contenido adicional
|
|
36
|
-
* </CardFooter>
|
|
37
|
-
* </Card>
|
|
38
|
-
* ```
|
|
17
|
+
* Composite card surface. Pass `title`, `description`, `action`, and `footer` as props
|
|
18
|
+
* to render the full structure without manually composing sub-components.
|
|
19
|
+
* Use `CardRoot` + primitives (`CardHeader`, `CardTitle`, `CardDescription`, `CardAction`,
|
|
20
|
+
* `CardContent`, `CardFooter`) directly for layouts the composite cannot express —
|
|
21
|
+
* for example, placing an `<img>` as the first child automatically clips it to the card's top corners.
|
|
22
|
+
* `CardAction` renders in the top-right of the header, spanning both title and description rows.
|
|
23
|
+
* `size="sm"` reduces padding and font size across all nested slots via `data-size` and CSS group selectors.
|
|
24
|
+
* Sub-components use `data-slot` attributes following the [Base UI](https://base-ui.com/react) slot convention.
|
|
39
25
|
*/
|
|
40
26
|
const meta: Meta<typeof Card> = {
|
|
41
|
-
title: "
|
|
27
|
+
title: "Components/Card",
|
|
42
28
|
component: Card,
|
|
43
|
-
tags: ["autodocs"],
|
|
44
29
|
subcomponents: {
|
|
30
|
+
CardRoot: CardRoot as ComponentType<unknown>,
|
|
45
31
|
CardHeader: CardHeader as ComponentType<unknown>,
|
|
46
32
|
CardTitle: CardTitle as ComponentType<unknown>,
|
|
47
33
|
CardDescription: CardDescription as ComponentType<unknown>,
|
|
34
|
+
CardAction: CardAction as ComponentType<unknown>,
|
|
48
35
|
CardContent: CardContent as ComponentType<unknown>,
|
|
49
36
|
CardFooter: CardFooter as ComponentType<unknown>,
|
|
50
37
|
},
|
|
38
|
+
args: {
|
|
39
|
+
className: "w-[350px]",
|
|
40
|
+
title: "How to Improve Your Productivity",
|
|
41
|
+
description: "By Jane Doe · 5 min read",
|
|
42
|
+
},
|
|
43
|
+
argTypes: {
|
|
44
|
+
classNames: { control: false },
|
|
45
|
+
},
|
|
46
|
+
tags: ["beta"],
|
|
47
|
+
parameters: { layout: "centered" },
|
|
51
48
|
};
|
|
52
49
|
|
|
53
50
|
export default meta;
|
|
54
|
-
|
|
55
51
|
type Story = StoryObj<typeof Card>;
|
|
56
52
|
|
|
57
53
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
54
|
+
* `className` styles the card root. `classNames` exposes the `header`,
|
|
55
|
+
* `title`, `description`, `action`, `content`, and `footer` slots for
|
|
56
|
+
* fine-grained styling without dropping to primitives.
|
|
60
57
|
*/
|
|
58
|
+
export const WithClassNames: Story = {
|
|
59
|
+
args: {
|
|
60
|
+
children: "Card body content.",
|
|
61
|
+
classNames: {
|
|
62
|
+
title: "text-primary",
|
|
63
|
+
description: "italic",
|
|
64
|
+
content: "text-base",
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
61
69
|
export const Default: Story = {
|
|
70
|
+
render: (args) => (
|
|
71
|
+
<Card
|
|
72
|
+
{...args}
|
|
73
|
+
footer={
|
|
74
|
+
<>
|
|
75
|
+
<Button variant="outline">Read more</Button>
|
|
76
|
+
<Button className="ml-auto">Save</Button>
|
|
77
|
+
</>
|
|
78
|
+
}
|
|
79
|
+
>
|
|
80
|
+
<p className="text-muted-foreground leading-relaxed">
|
|
81
|
+
Discover strategies and tools to boost your productivity. Learn to
|
|
82
|
+
manage your time effectively and stay focused on what matters.
|
|
83
|
+
</p>
|
|
84
|
+
</Card>
|
|
85
|
+
),
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const Sizes: Story = {
|
|
62
89
|
render: () => (
|
|
63
|
-
<
|
|
64
|
-
<
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
<CardDescription className="text-sm">By Jane Doe</CardDescription>
|
|
69
|
-
</CardHeader>
|
|
70
|
-
<CardContent className="p-4">
|
|
71
|
-
<p className="text-gray-700 text-base">
|
|
72
|
-
Discover the best strategies and tools to boost your productivity in
|
|
73
|
-
work and life. Learn how to manage your time effectively and stay
|
|
74
|
-
focused on what really matters.
|
|
90
|
+
<div className="flex flex-col gap-4 w-[350px]">
|
|
91
|
+
<Card title="Total Revenue" description="Compared to last month">
|
|
92
|
+
<p className="text-3xl font-bold">$45,231.89</p>
|
|
93
|
+
<p className="text-xs text-muted-foreground mt-1">
|
|
94
|
+
+20.1% from last month
|
|
75
95
|
</p>
|
|
76
|
-
</
|
|
77
|
-
<
|
|
78
|
-
<
|
|
79
|
-
<
|
|
80
|
-
</
|
|
96
|
+
</Card>
|
|
97
|
+
<Card size="sm" title="Active Users" description="Compared to last month">
|
|
98
|
+
<p className="text-3xl font-bold">2,350</p>
|
|
99
|
+
<p className="text-xs text-muted-foreground mt-1">+180 new users</p>
|
|
100
|
+
</Card>
|
|
101
|
+
</div>
|
|
102
|
+
),
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* `CardAction` anchors to the top-right corner of the header and spans both the title and description rows.
|
|
107
|
+
* Pass any node — icon button, badge, menu trigger — via the `action` prop.
|
|
108
|
+
*/
|
|
109
|
+
export const WithAction: Story = {
|
|
110
|
+
render: (args) => (
|
|
111
|
+
<Card
|
|
112
|
+
{...args}
|
|
113
|
+
title="Team Members"
|
|
114
|
+
description="Manage your workspace members"
|
|
115
|
+
action={
|
|
116
|
+
<Button variant="outline" size="sm">
|
|
117
|
+
Invite
|
|
118
|
+
</Button>
|
|
119
|
+
}
|
|
120
|
+
>
|
|
121
|
+
<div className="flex flex-col gap-2">
|
|
122
|
+
{Array.from({ length: 3 }).map((_, i) => (
|
|
123
|
+
<div key={i} className="flex items-center justify-between">
|
|
124
|
+
<span className="text-sm">{faker.person.fullName()}</span>
|
|
125
|
+
<span className="text-xs text-muted-foreground">
|
|
126
|
+
{faker.internet.email()}
|
|
127
|
+
</span>
|
|
128
|
+
</div>
|
|
129
|
+
))}
|
|
130
|
+
</div>
|
|
131
|
+
</Card>
|
|
132
|
+
),
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export const WithFooter: Story = {
|
|
136
|
+
render: (args) => (
|
|
137
|
+
<Card
|
|
138
|
+
{...args}
|
|
139
|
+
title="Delete Account"
|
|
140
|
+
description="This action is permanent and cannot be undone."
|
|
141
|
+
footer={
|
|
142
|
+
<div className="flex gap-2 w-full justify-end">
|
|
143
|
+
<Button variant="outline">Cancel</Button>
|
|
144
|
+
<Button variant="error">Delete</Button>
|
|
145
|
+
</div>
|
|
146
|
+
}
|
|
147
|
+
>
|
|
148
|
+
<p className="text-muted-foreground text-sm leading-relaxed">
|
|
149
|
+
All your data, settings, and integrations will be permanently removed
|
|
150
|
+
from our servers.
|
|
151
|
+
</p>
|
|
81
152
|
</Card>
|
|
82
153
|
),
|
|
83
154
|
};
|
|
84
155
|
|
|
85
156
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
157
|
+
* Use `CardRoot` + sub-components for layouts the composite cannot express.
|
|
158
|
+
* An `<img>` placed as the first child of `CardRoot` auto-clips to the card's top-rounded corners
|
|
159
|
+
* via the `has-[>img:first-child]:pt-0` and `*:[img:first-child]:rounded-t-xl` selectors.
|
|
88
160
|
*/
|
|
89
|
-
export const
|
|
161
|
+
export const Primitive: Story = {
|
|
90
162
|
render: () => (
|
|
91
|
-
<
|
|
163
|
+
<CardRoot className="w-[350px]">
|
|
92
164
|
<img
|
|
93
|
-
src={faker.image.
|
|
94
|
-
alt="
|
|
165
|
+
src={faker.image.urlPicsumPhotos({ width: 350, height: 150 })}
|
|
166
|
+
alt="Villa Luxury"
|
|
95
167
|
className="w-full h-[150px] object-cover"
|
|
96
168
|
/>
|
|
97
|
-
<
|
|
98
|
-
<CardTitle
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
</
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
</
|
|
169
|
+
<CardHeader>
|
|
170
|
+
<CardTitle>Villa Luxury</CardTitle>
|
|
171
|
+
<CardDescription>Ocean views and private pool.</CardDescription>
|
|
172
|
+
<CardAction>
|
|
173
|
+
<Button variant="ghost" size="sm">
|
|
174
|
+
♡
|
|
175
|
+
</Button>
|
|
176
|
+
</CardAction>
|
|
177
|
+
</CardHeader>
|
|
178
|
+
<CardContent>
|
|
179
|
+
<span className="text-2xl font-semibold">$750</span>
|
|
180
|
+
<span className="ml-2 text-sm text-muted-foreground">/ night</span>
|
|
109
181
|
</CardContent>
|
|
110
|
-
<CardFooter
|
|
111
|
-
<Button variant="outline">
|
|
112
|
-
<Button className="
|
|
182
|
+
<CardFooter>
|
|
183
|
+
<Button variant="outline">Details</Button>
|
|
184
|
+
<Button className="ml-auto">Book</Button>
|
|
113
185
|
</CardFooter>
|
|
114
|
-
</
|
|
186
|
+
</CardRoot>
|
|
115
187
|
),
|
|
116
188
|
};
|