@vvedantb/ui 0.17.0 → 0.18.0
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/README.md +253 -3
- package/package.json +13 -5
- package/shell/src/SidebarResize.tsx +1 -4
- package/src/ai-elements/__snapshots__/queue.test.tsx.snap +11 -0
- package/src/ai-elements/activity-question-utils.ts +26 -0
- package/src/ai-elements/activity-question.tsx +159 -0
- package/src/ai-elements/activity-shared.tsx +459 -0
- package/src/ai-elements/activity-step-detail.tsx +115 -0
- package/src/ai-elements/activity-step-label.ts +981 -0
- package/src/ai-elements/activity-tasks-utils.ts +199 -0
- package/src/ai-elements/activity-tasks.tsx +770 -0
- package/src/ai-elements/artifact.tsx +162 -52
- package/src/ai-elements/attachments.tsx +62 -32
- package/src/ai-elements/code-block.tsx +76 -22
- package/src/ai-elements/context.tsx +370 -0
- package/src/ai-elements/conversation.tsx +186 -62
- package/src/ai-elements/file-chip.tsx +53 -0
- package/src/ai-elements/message.tsx +161 -50
- package/src/ai-elements/model-picker-content.tsx +505 -0
- package/src/ai-elements/model-picker-types.ts +56 -0
- package/src/ai-elements/model-picker.tsx +301 -0
- package/src/ai-elements/model-selector.tsx +102 -21
- package/src/ai-elements/plan.tsx +130 -0
- package/src/ai-elements/prompt-input-settings.tsx +144 -0
- package/src/ai-elements/prompt-input-speech.tsx +189 -0
- package/src/ai-elements/prompt-input-traits.tsx +281 -0
- package/src/ai-elements/prompt-input.tsx +320 -134
- package/src/ai-elements/provider-icon.tsx +133 -0
- package/src/ai-elements/queue.tsx +293 -9
- package/src/ai-elements/shimmer.tsx +72 -1
- package/src/ai-elements/simple-model-ladder.tsx +108 -0
- package/src/ai-elements/test-results.tsx +510 -0
- package/src/ai-elements/trace.tsx +243 -0
- package/src/ai-elements/use-streaming-disclosure.ts +50 -0
- package/src/ai-elements/web-preview.tsx +349 -0
- package/src/composer/input-area/ComposerSlashMenu.tsx +1 -1
- package/src/index.ts +66 -12
- package/src/kibo/avatar-stack.tsx +40 -0
- package/src/kibo/contribution-graph.tsx +539 -0
- package/src/kibo/cursor.tsx +72 -0
- package/src/kibo/gantt-features.tsx +439 -0
- package/src/kibo/gantt-header.tsx +181 -0
- package/src/kibo/gantt-provider.tsx +657 -0
- package/src/kibo/gantt-sidebar.tsx +157 -0
- package/src/kibo/gantt-timeline.tsx +154 -0
- package/src/kibo/index.ts +151 -0
- package/src/kibo/kanban.tsx +338 -0
- package/src/kibo/kanbanDropTarget.ts +25 -0
- package/src/kibo/list.tsx +162 -0
- package/src/kibo/theme-switcher.tsx +109 -0
- package/src/kibo/video-player.tsx +237 -0
- package/src/markdown/Markdown.tsx +112 -0
- package/src/markdown/MarkdownBlockquote.tsx +45 -0
- package/src/markdown/MarkdownCodeBlock.tsx +197 -0
- package/src/markdown/MarkdownInlineCode.tsx +40 -0
- package/src/markdown/MarkdownLink.tsx +92 -0
- package/src/markdown/codeFence.ts +57 -0
- package/src/markdown/codeHighlight.ts +138 -0
- package/src/markdown/incrementalBlocks.ts +182 -0
- package/src/markdown/index.ts +3 -0
- package/src/markdown/markdownClipboard.ts +178 -0
- package/src/markdown/mermaid.ts +155 -0
- package/src/markdown/rehypeGithubAlerts.ts +80 -0
- package/src/motion/presets.ts +24 -0
- package/src/ui/__snapshots__/alt-wrappers.test.tsx.snap +19 -0
- package/src/ui/__snapshots__/sonner-dom.test.tsx.snap +5 -0
- package/src/ui/_menu-parts.tsx +29 -0
- package/src/ui/accordion.alt.tsx +46 -53
- package/src/ui/accordion.tsx +59 -10
- package/src/ui/avatar.tsx +56 -0
- package/src/ui/badge.tsx +45 -21
- package/src/ui/border-beam.tsx +97 -0
- package/src/ui/button-group.tsx +50 -21
- package/src/ui/button.tsx +67 -45
- package/src/ui/calendar.tsx +48 -5
- package/src/ui/card.tsx +77 -8
- package/src/ui/carousel.tsx +60 -0
- package/src/ui/centered-spinner.tsx +30 -0
- package/src/ui/checkbox.tsx +34 -16
- package/src/ui/clear-input.alt.tsx +33 -75
- package/src/ui/clear-input.tsx +140 -15
- package/src/ui/collapsible.tsx +40 -2
- package/src/ui/command.tsx +143 -70
- package/src/ui/context-menu.tsx +159 -93
- package/src/ui/crossfade-icon.tsx +123 -0
- package/src/ui/dialog.tsx +154 -70
- package/src/ui/dropdown-menu.tsx +177 -117
- package/src/ui/hover-card.tsx +59 -17
- package/src/ui/input-group.alt.tsx +83 -83
- package/src/ui/input-group.tsx +129 -58
- package/src/ui/input.tsx +13 -4
- package/src/ui/label.tsx +11 -8
- package/src/ui/list-row.tsx +232 -0
- package/src/ui/loading-state.tsx +215 -0
- package/src/ui/popover.tsx +56 -23
- package/src/ui/progress.tsx +28 -16
- package/src/ui/refresh-spin-icon.tsx +41 -0
- package/src/ui/search-input.tsx +66 -15
- package/src/ui/select.tsx +171 -100
- package/src/ui/separator.tsx +23 -14
- package/src/ui/slider.tsx +21 -7
- package/src/ui/sonner.tsx +68 -30
- package/src/ui/spinner.tsx +229 -11
- package/src/ui/status-dot.tsx +84 -0
- package/src/ui/surface.tsx +49 -0
- package/src/ui/switch.tsx +29 -15
- package/src/ui/table.tsx +38 -16
- package/src/ui/tabs.tsx +123 -35
- package/src/ui/tabsSliding.ts +33 -1
- package/src/ui/textarea.tsx +13 -4
- package/src/ui/tooltip.tsx +50 -14
- package/src/ui/ui-config.tsx +149 -0
- package/src/utils/cat-color.ts +123 -0
- package/src/utils/dayjs.ts +6 -0
- package/src/utils/gesture.ts +59 -0
- package/src/utils/runtimeVisibility.ts +167 -0
- package/src/utils/sharedClock.ts +96 -0
- package/src/utils/surface-radius.ts +16 -0
- package/src/utils/useDragSensors.ts +58 -0
- package/tokens/examples/tokens-moved/Fixture.tsx +283 -0
- package/tokens/examples/tokens-moved/README.md +20 -0
- package/tokens/examples/tokens-moved/index.html +12 -0
- package/tokens/examples/tokens-moved/main.tsx +6 -0
- package/tokens/examples/tokens-moved/package.json +14 -0
- package/tokens/examples/tokens-moved/styles.css +12 -0
- package/tokens/examples/tokens-moved/tsconfig.json +14 -0
- package/tokens/examples/tokens-moved/vite.config.ts +11 -0
- package/tokens/src/css/border-beam.css +116 -0
- package/tokens/src/css/compat-shadcn-rgb.css +6 -0
- package/tokens/src/css/markdown.css +318 -0
package/src/ui/badge.tsx
CHANGED
|
@@ -1,26 +1,36 @@
|
|
|
1
1
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
-
import {
|
|
2
|
+
import { useCn, useUiConfig } from "./ui-config";
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
"inline-flex items-center gap-1 rounded-full border border-transparent px-2.5 py-1 text-xs font-medium tracking-normal transition-colors focus:outline-none focus:ring-2 focus:ring-focus-ring focus:ring-offset-2 focus:ring-offset-background"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
4
|
+
const badgeBaseClasses =
|
|
5
|
+
"inline-flex items-center gap-1 rounded-full border border-transparent px-2.5 py-1 text-xs font-medium tracking-normal transition-colors focus:outline-none focus:ring-2 focus:ring-focus-ring focus:ring-offset-2 focus:ring-offset-background";
|
|
6
|
+
|
|
7
|
+
const badgeVariantClasses = {
|
|
8
|
+
default: "border-border bg-default text-foreground",
|
|
9
|
+
secondary: "border-separator bg-default text-default-foreground",
|
|
10
|
+
destructive: "border-danger/25 bg-danger/12 text-danger",
|
|
11
|
+
// Neutral border with a faint danger tint, e.g. a revoked or expired status.
|
|
12
|
+
"destructive-soft": "border-border bg-danger/10 text-danger",
|
|
13
|
+
outline: "border-separator bg-transparent text-muted-foreground",
|
|
14
|
+
success: "border-success/25 bg-success/12 text-success",
|
|
15
|
+
warning: "border-warning/25 bg-warning/12 text-warning",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const badgeVariants = cva(badgeBaseClasses, {
|
|
19
|
+
variants: {
|
|
20
|
+
variant: badgeVariantClasses,
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: {
|
|
23
|
+
variant: "default",
|
|
22
24
|
},
|
|
23
|
-
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/** `UiConfig.Badge`: each given class string replaces the built-in one. */
|
|
28
|
+
export interface BadgeConfig {
|
|
29
|
+
classes?: {
|
|
30
|
+
base?: string;
|
|
31
|
+
variant?: Partial<Record<keyof typeof badgeVariantClasses, string>>;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
24
34
|
|
|
25
35
|
export interface BadgeProps
|
|
26
36
|
extends
|
|
@@ -28,8 +38,22 @@ export interface BadgeProps
|
|
|
28
38
|
VariantProps<typeof badgeVariants> {}
|
|
29
39
|
|
|
30
40
|
function Badge({ className, variant, ...props }: BadgeProps) {
|
|
41
|
+
const classes = useUiConfig("Badge")?.classes;
|
|
42
|
+
const merge = useCn();
|
|
43
|
+
const v = variant ?? "default";
|
|
31
44
|
return (
|
|
32
|
-
<div
|
|
45
|
+
<div
|
|
46
|
+
className={
|
|
47
|
+
classes
|
|
48
|
+
? merge(
|
|
49
|
+
classes.base ?? badgeBaseClasses,
|
|
50
|
+
classes.variant?.[v] ?? badgeVariantClasses[v],
|
|
51
|
+
className,
|
|
52
|
+
)
|
|
53
|
+
: merge(badgeVariants({ variant }), className)
|
|
54
|
+
}
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
33
57
|
);
|
|
34
58
|
}
|
|
35
59
|
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef, ReactNode } from "react";
|
|
2
|
+
import { AnimatePresence, m } from "motion/react";
|
|
3
|
+
|
|
4
|
+
import { cn } from "../utils/cn";
|
|
5
|
+
import { tokenTransition } from "../motion/presets";
|
|
6
|
+
import { assignRef } from "../utils/ref";
|
|
7
|
+
import { useAnimOffscreenRef } from "../utils/runtimeVisibility";
|
|
8
|
+
import { useCn } from "./ui-config";
|
|
9
|
+
|
|
10
|
+
export type BorderBeamSize = "sm" | "md" | "lg";
|
|
11
|
+
|
|
12
|
+
/** Beam colour: neutral, the in-progress hue, or a two-colour sweep. */
|
|
13
|
+
export type BorderBeamColorVariant = "mono" | "progress" | "colorful";
|
|
14
|
+
|
|
15
|
+
export type BorderBeamProps = ComponentPropsWithRef<"div"> & {
|
|
16
|
+
children: ReactNode;
|
|
17
|
+
/**
|
|
18
|
+
* Runs the beam while true. Toggle this rather than mounting/unmounting
|
|
19
|
+
* <BorderBeam/> — the wrapper is a real DOM node, so conditional rendering
|
|
20
|
+
* would remount the whole subtree.
|
|
21
|
+
*/
|
|
22
|
+
active: boolean;
|
|
23
|
+
size?: BorderBeamSize;
|
|
24
|
+
colorVariant?: BorderBeamColorVariant;
|
|
25
|
+
/**
|
|
26
|
+
* Wrapper classes. Give it the same radius as the child (e.g. `rounded-control`)
|
|
27
|
+
* — the beam inherits the wrapper's radius.
|
|
28
|
+
*/
|
|
29
|
+
className?: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// `beam-*`, not `border-beam-*` — tailwind-merge reads a `border-` prefix as a
|
|
33
|
+
// border-colour utility and would drop the base class as a variant conflict.
|
|
34
|
+
const sizeClass: Record<BorderBeamSize, string> = {
|
|
35
|
+
sm: "beam-sm",
|
|
36
|
+
md: "",
|
|
37
|
+
lg: "beam-lg",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const colorClass: Record<BorderBeamColorVariant, string> = {
|
|
41
|
+
mono: "",
|
|
42
|
+
progress: "beam-progress",
|
|
43
|
+
colorful: "beam-colorful",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Wraps a surface in a hairline of light that travels around its border while
|
|
48
|
+
* `active`. The child needs an opaque background: it is what hides the
|
|
49
|
+
* gradient's interior so only the ring reads.
|
|
50
|
+
*
|
|
51
|
+
* The beam is CSS: import `@vvedantb/ui/tokens/css/border-beam.css` (or ship
|
|
52
|
+
* your own `.beam-*` rules). Colours are CSS variables on the wrapper or any
|
|
53
|
+
* ancestor: `--beam-color` (mono), `--beam-progress-color` (progress) and
|
|
54
|
+
* `--beam-colorful-from` / `--beam-colorful-to` (colorful).
|
|
55
|
+
*
|
|
56
|
+
* Spreads the rest props and ref onto the wrapper div so the whole thing can
|
|
57
|
+
* sit under a Radix `asChild` trigger.
|
|
58
|
+
*/
|
|
59
|
+
export function BorderBeam({
|
|
60
|
+
children,
|
|
61
|
+
active,
|
|
62
|
+
size = "md",
|
|
63
|
+
colorVariant = "mono",
|
|
64
|
+
className,
|
|
65
|
+
ref,
|
|
66
|
+
...rest
|
|
67
|
+
}: BorderBeamProps) {
|
|
68
|
+
const setOffscreen = useAnimOffscreenRef<HTMLDivElement>();
|
|
69
|
+
const merge = useCn();
|
|
70
|
+
return (
|
|
71
|
+
<div
|
|
72
|
+
className={merge("relative", className)}
|
|
73
|
+
ref={(node) => {
|
|
74
|
+
setOffscreen(node);
|
|
75
|
+
assignRef(ref, node);
|
|
76
|
+
}}
|
|
77
|
+
{...rest}
|
|
78
|
+
>
|
|
79
|
+
<AnimatePresence initial={false}>
|
|
80
|
+
{active ? (
|
|
81
|
+
<m.span
|
|
82
|
+
aria-hidden="true"
|
|
83
|
+
data-anim-chrome=""
|
|
84
|
+
className={cn("beam-root", sizeClass[size], colorClass[colorVariant])}
|
|
85
|
+
initial={{ opacity: 0 }}
|
|
86
|
+
animate={{ opacity: 1 }}
|
|
87
|
+
exit={{ opacity: 0 }}
|
|
88
|
+
transition={tokenTransition.slow}
|
|
89
|
+
>
|
|
90
|
+
<span className="beam" />
|
|
91
|
+
</m.span>
|
|
92
|
+
) : null}
|
|
93
|
+
</AnimatePresence>
|
|
94
|
+
{children}
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
}
|
package/src/ui/button-group.tsx
CHANGED
|
@@ -2,37 +2,60 @@ import type * as React from "react";
|
|
|
2
2
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
3
|
import { Slot } from "radix-ui";
|
|
4
4
|
|
|
5
|
-
import { cn } from "../utils/cn";
|
|
6
5
|
import { Separator } from "./separator";
|
|
6
|
+
import { useCn, useUiConfig } from "./ui-config";
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
{
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
8
|
+
/** `UiConfig.ButtonGroup`: each given class string replaces the built-in one. */
|
|
9
|
+
export interface ButtonGroupConfig {
|
|
10
|
+
classes?: {
|
|
11
|
+
base?: string;
|
|
12
|
+
orientation?: Partial<Record<"horizontal" | "vertical", string>>;
|
|
13
|
+
text?: string;
|
|
14
|
+
separator?: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const buttonGroupBaseClasses =
|
|
19
|
+
"flex w-fit items-stretch focus-visible:*:z-10 focus-visible:*:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 [&>[data-slot=select-trigger]:last-of-type]:has-[select[aria-hidden=true]:last-child]:rounded-r-md has-[>[data-slot=button-group]]:gap-2";
|
|
20
|
+
|
|
21
|
+
const buttonGroupOrientationClasses = {
|
|
22
|
+
horizontal:
|
|
23
|
+
"[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none",
|
|
24
|
+
vertical:
|
|
25
|
+
"flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const buttonGroupVariants = cva(buttonGroupBaseClasses, {
|
|
29
|
+
variants: {
|
|
30
|
+
orientation: buttonGroupOrientationClasses,
|
|
31
|
+
},
|
|
32
|
+
defaultVariants: {
|
|
33
|
+
orientation: "horizontal",
|
|
22
34
|
},
|
|
23
|
-
);
|
|
35
|
+
});
|
|
24
36
|
|
|
25
37
|
function ButtonGroup({
|
|
26
38
|
className,
|
|
27
39
|
orientation,
|
|
28
40
|
...props
|
|
29
41
|
}: React.ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>) {
|
|
42
|
+
const classes = useUiConfig("ButtonGroup")?.classes;
|
|
43
|
+
const merge = useCn();
|
|
44
|
+
const o = orientation ?? "horizontal";
|
|
30
45
|
return (
|
|
31
46
|
<div
|
|
32
47
|
role="group"
|
|
33
48
|
data-slot="button-group"
|
|
34
49
|
data-orientation={orientation}
|
|
35
|
-
className={
|
|
50
|
+
className={
|
|
51
|
+
classes
|
|
52
|
+
? merge(
|
|
53
|
+
classes.base ?? buttonGroupBaseClasses,
|
|
54
|
+
classes.orientation?.[o] ?? buttonGroupOrientationClasses[o],
|
|
55
|
+
className,
|
|
56
|
+
)
|
|
57
|
+
: merge(buttonGroupVariants({ orientation }), className)
|
|
58
|
+
}
|
|
36
59
|
{...props}
|
|
37
60
|
/>
|
|
38
61
|
);
|
|
@@ -46,11 +69,14 @@ function ButtonGroupText({
|
|
|
46
69
|
asChild?: boolean;
|
|
47
70
|
}) {
|
|
48
71
|
const Comp = asChild ? Slot.Root : "div";
|
|
72
|
+
const base = useUiConfig("ButtonGroup")?.classes?.text;
|
|
73
|
+
const merge = useCn();
|
|
49
74
|
|
|
50
75
|
return (
|
|
51
76
|
<Comp
|
|
52
|
-
className={
|
|
53
|
-
|
|
77
|
+
className={merge(
|
|
78
|
+
base ??
|
|
79
|
+
"bg-surface-secondary flex items-center gap-2 rounded-md px-4 text-sm font-medium [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
|
54
80
|
className,
|
|
55
81
|
)}
|
|
56
82
|
{...props}
|
|
@@ -63,12 +89,15 @@ function ButtonGroupSeparator({
|
|
|
63
89
|
orientation = "vertical",
|
|
64
90
|
...props
|
|
65
91
|
}: React.ComponentProps<typeof Separator>) {
|
|
92
|
+
const base = useUiConfig("ButtonGroup")?.classes?.separator;
|
|
93
|
+
const merge = useCn();
|
|
66
94
|
return (
|
|
67
95
|
<Separator
|
|
68
96
|
data-slot="button-group-separator"
|
|
69
97
|
orientation={orientation}
|
|
70
|
-
className={
|
|
71
|
-
|
|
98
|
+
className={merge(
|
|
99
|
+
base ??
|
|
100
|
+
"bg-separator relative m-0! self-stretch data-[orientation=vertical]:h-auto",
|
|
72
101
|
className,
|
|
73
102
|
)}
|
|
74
103
|
{...props}
|
package/src/ui/button.tsx
CHANGED
|
@@ -1,45 +1,63 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { Slot } from "@radix-ui/react-slot";
|
|
3
3
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
-
import {
|
|
4
|
+
import { Spinner } from "./spinner";
|
|
5
|
+
import { useCn, useUiConfig } from "./ui-config";
|
|
5
6
|
|
|
6
7
|
const ghostClass =
|
|
7
8
|
"text-muted-foreground hover:bg-surface-tertiary hover:text-foreground";
|
|
8
9
|
|
|
9
|
-
const
|
|
10
|
-
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium tracking-normal transition-[transform,background-color,box-shadow,opacity,border-color] duration-200 ease-smooth focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 active:scale-[0.96] [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
10
|
+
const buttonBaseClasses =
|
|
11
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium tracking-normal transition-[transform,background-color,box-shadow,opacity,border-color] duration-200 ease-smooth focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 active:scale-[0.96] [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0";
|
|
12
|
+
|
|
13
|
+
const buttonVariantClasses = {
|
|
14
|
+
default:
|
|
15
|
+
"bg-primary text-primary-foreground hover:-translate-y-0.5 hover:opacity-90",
|
|
16
|
+
destructive:
|
|
17
|
+
"bg-[oklch(47.2%_0.195_25.7)] text-white hover:bg-[oklch(42.5%_0.185_25.7)]",
|
|
18
|
+
outline:
|
|
19
|
+
"border border-border bg-transparent text-foreground hover:bg-default",
|
|
20
|
+
secondary: "bg-default text-default-foreground hover:bg-default/78",
|
|
21
|
+
ghost: ghostClass,
|
|
22
|
+
link: "text-primary underline-offset-4 hover:underline active:scale-100",
|
|
23
|
+
// Ghost plus an override; tailwind-merge drops the clashing ghost classes.
|
|
24
|
+
flat: `${ghostClass} bg-transparent text-muted hover:bg-surface-tertiary hover:text-foreground`, // check-tokens-allow: text-muted
|
|
25
|
+
success: `${ghostClass} bg-success text-success-foreground hover:bg-success/90`,
|
|
26
|
+
warning: `${ghostClass} bg-warning text-warning-foreground hover:bg-warning/90`,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const buttonSizeClasses = {
|
|
30
|
+
default: "h-10 px-4 py-2",
|
|
31
|
+
xs: "h-7 rounded-md px-2 text-xs hit-target [&_svg:not([class*='size-'])]:size-3.5",
|
|
32
|
+
sm: "h-8 rounded-lg px-3 text-xs max-sm:hit-target",
|
|
33
|
+
lg: "h-11 px-6",
|
|
34
|
+
icon: "h-10 w-10",
|
|
35
|
+
"icon-sm": "h-8 w-8 rounded-lg max-sm:hit-target",
|
|
36
|
+
"icon-xs": "h-7 w-7 rounded-md hit-target",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const buttonVariants = cva(buttonBaseClasses, {
|
|
40
|
+
variants: {
|
|
41
|
+
variant: buttonVariantClasses,
|
|
42
|
+
size: buttonSizeClasses,
|
|
41
43
|
},
|
|
42
|
-
|
|
44
|
+
defaultVariants: {
|
|
45
|
+
variant: "default",
|
|
46
|
+
size: "default",
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
type ButtonVariant = keyof typeof buttonVariantClasses;
|
|
51
|
+
type ButtonSize = keyof typeof buttonSizeClasses;
|
|
52
|
+
|
|
53
|
+
/** `UiConfig.Button`: each given class string replaces the built-in one. */
|
|
54
|
+
export interface ButtonConfig {
|
|
55
|
+
classes?: {
|
|
56
|
+
base?: string;
|
|
57
|
+
variant?: Partial<Record<ButtonVariant, string>>;
|
|
58
|
+
size?: Partial<Record<ButtonSize, string>>;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
43
61
|
|
|
44
62
|
export interface ButtonProps
|
|
45
63
|
extends
|
|
@@ -54,31 +72,35 @@ export interface ButtonProps
|
|
|
54
72
|
loading?: boolean;
|
|
55
73
|
}
|
|
56
74
|
|
|
57
|
-
function ButtonSpinner() {
|
|
58
|
-
return (
|
|
59
|
-
<svg className="h-4 w-4 animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
60
|
-
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
|
61
|
-
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c3.042 1.135 5.824 3 7.938l3-2.647z" />
|
|
62
|
-
</svg>
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
75
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
67
76
|
(
|
|
68
77
|
{ className, variant, size, asChild = false, startContent, endContent, loading, children, ...props },
|
|
69
78
|
ref,
|
|
70
79
|
) => {
|
|
71
80
|
const Comp = asChild ? Slot : "button";
|
|
81
|
+
const classes = useUiConfig("Button")?.classes;
|
|
82
|
+
const merge = useCn();
|
|
83
|
+
const v = variant ?? "default";
|
|
84
|
+
const s = size ?? "default";
|
|
72
85
|
return (
|
|
73
86
|
<Comp
|
|
74
|
-
className={
|
|
87
|
+
className={
|
|
88
|
+
classes
|
|
89
|
+
? merge(
|
|
90
|
+
classes.base ?? buttonBaseClasses,
|
|
91
|
+
classes.variant?.[v] ?? buttonVariantClasses[v],
|
|
92
|
+
classes.size?.[s] ?? buttonSizeClasses[s],
|
|
93
|
+
className,
|
|
94
|
+
)
|
|
95
|
+
: merge(buttonVariants({ variant, size, className }))
|
|
96
|
+
}
|
|
75
97
|
ref={ref}
|
|
76
98
|
{...props}
|
|
77
99
|
{...(loading ? { disabled: true, "data-loading": "true" } : null)}
|
|
78
100
|
>
|
|
79
101
|
{asChild ? children : (
|
|
80
102
|
<>
|
|
81
|
-
{loading ? <
|
|
103
|
+
{loading ? <Spinner variant="ring" /> : startContent}
|
|
82
104
|
{children}
|
|
83
105
|
{endContent}
|
|
84
106
|
</>
|
package/src/ui/calendar.tsx
CHANGED
|
@@ -6,6 +6,9 @@ import {
|
|
|
6
6
|
} from "@tabler/icons-react";
|
|
7
7
|
import {
|
|
8
8
|
type Chevron,
|
|
9
|
+
type ClassNames,
|
|
10
|
+
type CustomComponents,
|
|
11
|
+
type DayButton,
|
|
9
12
|
DayPicker,
|
|
10
13
|
type Root,
|
|
11
14
|
getDefaultClassNames,
|
|
@@ -14,6 +17,18 @@ import { cn } from "../utils/cn";
|
|
|
14
17
|
import {
|
|
15
18
|
pickerFocusClass,
|
|
16
19
|
} from "./_picker-classes";
|
|
20
|
+
import { useCn, useUiConfig } from "./ui-config";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* `UiConfig.Calendar`. `classes.base` replaces the built-in root `className`
|
|
24
|
+
* classes; every other key replaces the built-in value of that DayPicker
|
|
25
|
+
* `classNames` key (a caller `classNames` still wins). `components` is the
|
|
26
|
+
* default for the prop, applied over the built-in Root and Chevron.
|
|
27
|
+
*/
|
|
28
|
+
export interface CalendarConfig {
|
|
29
|
+
classes?: Partial<ClassNames> & { base?: string };
|
|
30
|
+
components?: Partial<CustomComponents>;
|
|
31
|
+
}
|
|
17
32
|
|
|
18
33
|
// shadcn's v9 calendar on package tokens: no background of its own (it takes
|
|
19
34
|
// the popover or card surface it sits on), selected days use the picker
|
|
@@ -57,14 +72,19 @@ function Calendar({
|
|
|
57
72
|
...props
|
|
58
73
|
}: React.ComponentProps<typeof DayPicker>) {
|
|
59
74
|
const defaultClassNames = getDefaultClassNames();
|
|
75
|
+
const config = useUiConfig("Calendar");
|
|
76
|
+
const merge = useCn();
|
|
77
|
+
const { base, ...configClassNames } = config?.classes ?? {};
|
|
60
78
|
|
|
61
79
|
return (
|
|
62
80
|
<DayPicker
|
|
63
81
|
showOutsideDays={showOutsideDays}
|
|
64
|
-
className={
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
82
|
+
className={merge(
|
|
83
|
+
base ?? [
|
|
84
|
+
"group/calendar p-1 text-foreground [--cell-size:--spacing(8)]",
|
|
85
|
+
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
|
|
86
|
+
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
|
|
87
|
+
],
|
|
68
88
|
className,
|
|
69
89
|
)}
|
|
70
90
|
captionLayout={captionLayout}
|
|
@@ -146,11 +166,13 @@ function Calendar({
|
|
|
146
166
|
range_middle: cn("rounded-none", defaultClassNames.range_middle),
|
|
147
167
|
range_end: cn("rounded-r-lg", defaultClassNames.range_end),
|
|
148
168
|
hidden: cn("invisible", defaultClassNames.hidden),
|
|
169
|
+
...configClassNames,
|
|
149
170
|
...classNames,
|
|
150
171
|
}}
|
|
151
172
|
components={{
|
|
152
173
|
Root: CalendarRoot,
|
|
153
174
|
Chevron: CalendarChevron,
|
|
175
|
+
...config?.components,
|
|
154
176
|
...components,
|
|
155
177
|
}}
|
|
156
178
|
{...props}
|
|
@@ -158,4 +180,25 @@ function Calendar({
|
|
|
158
180
|
);
|
|
159
181
|
}
|
|
160
182
|
|
|
161
|
-
|
|
183
|
+
/**
|
|
184
|
+
* DayPicker's day button plus `data-selected`, so its classes can style the
|
|
185
|
+
* selected day on the button itself (`data-[selected=true]:bg-primary`).
|
|
186
|
+
* Pass it as `components.DayButton` (or through `UiConfig.Calendar.components`).
|
|
187
|
+
*/
|
|
188
|
+
function CalendarDayButton({
|
|
189
|
+
day: _day,
|
|
190
|
+
modifiers,
|
|
191
|
+
...props
|
|
192
|
+
}: React.ComponentProps<typeof DayButton>) {
|
|
193
|
+
const ref = React.useRef<HTMLButtonElement>(null);
|
|
194
|
+
|
|
195
|
+
// DayPicker decides which day has focus; moving DOM focus to match is what
|
|
196
|
+
// its own DayButton does.
|
|
197
|
+
React.useEffect(() => {
|
|
198
|
+
if (modifiers.focused) ref.current?.focus();
|
|
199
|
+
}, [modifiers.focused]);
|
|
200
|
+
|
|
201
|
+
return <button ref={ref} data-selected={modifiers.selected} {...props} />;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export { Calendar, CalendarDayButton };
|
package/src/ui/card.tsx
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { useCn, useUiConfig } from "./ui-config";
|
|
3
|
+
|
|
4
|
+
export interface CardConfig {
|
|
5
|
+
/** Replaces the built-in classes of a part. A part left out keeps its default. */
|
|
6
|
+
classes?: {
|
|
7
|
+
root?: string;
|
|
8
|
+
header?: string;
|
|
9
|
+
title?: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
content?: string;
|
|
12
|
+
footer?: string;
|
|
13
|
+
action?: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** The app's class string for `part` (else `fallback`), merged with the caller `className`. */
|
|
18
|
+
function useCardClass(
|
|
19
|
+
part: keyof NonNullable<CardConfig["classes"]>,
|
|
20
|
+
fallback: string,
|
|
21
|
+
className: string | undefined,
|
|
22
|
+
): string {
|
|
23
|
+
const merge = useCn();
|
|
24
|
+
return merge(useUiConfig("Card")?.classes?.[part] ?? fallback, className);
|
|
25
|
+
}
|
|
3
26
|
|
|
4
27
|
const Card = React.forwardRef<
|
|
5
28
|
HTMLDivElement,
|
|
@@ -7,10 +30,7 @@ const Card = React.forwardRef<
|
|
|
7
30
|
>(({ className, ...props }, ref) => (
|
|
8
31
|
<div
|
|
9
32
|
ref={ref}
|
|
10
|
-
className={
|
|
11
|
-
"rounded-lg bg-surface-card text-surface-foreground",
|
|
12
|
-
className,
|
|
13
|
-
)}
|
|
33
|
+
className={useCardClass("root", "rounded-lg bg-surface-card text-surface-foreground", className)}
|
|
14
34
|
{...props}
|
|
15
35
|
/>
|
|
16
36
|
));
|
|
@@ -22,18 +42,67 @@ const CardHeader = React.forwardRef<
|
|
|
22
42
|
>(({ className, ...props }, ref) => (
|
|
23
43
|
<div
|
|
24
44
|
ref={ref}
|
|
25
|
-
className={
|
|
45
|
+
className={useCardClass("header", "flex flex-col space-y-1.5 p-6", className)}
|
|
26
46
|
{...props}
|
|
27
47
|
/>
|
|
28
48
|
));
|
|
29
49
|
CardHeader.displayName = "CardHeader";
|
|
30
50
|
|
|
51
|
+
const CardTitle = React.forwardRef<
|
|
52
|
+
HTMLDivElement,
|
|
53
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
54
|
+
>(({ className, ...props }, ref) => (
|
|
55
|
+
<div
|
|
56
|
+
ref={ref}
|
|
57
|
+
className={useCardClass("title", "font-semibold leading-none tracking-[-0.02em]", className)}
|
|
58
|
+
{...props}
|
|
59
|
+
/>
|
|
60
|
+
));
|
|
61
|
+
CardTitle.displayName = "CardTitle";
|
|
62
|
+
|
|
63
|
+
const CardDescription = React.forwardRef<
|
|
64
|
+
HTMLDivElement,
|
|
65
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
66
|
+
>(({ className, ...props }, ref) => (
|
|
67
|
+
<div
|
|
68
|
+
ref={ref}
|
|
69
|
+
className={useCardClass("description", "text-sm text-muted-foreground", className)}
|
|
70
|
+
{...props}
|
|
71
|
+
/>
|
|
72
|
+
));
|
|
73
|
+
CardDescription.displayName = "CardDescription";
|
|
74
|
+
|
|
31
75
|
const CardContent = React.forwardRef<
|
|
32
76
|
HTMLDivElement,
|
|
33
77
|
React.HTMLAttributes<HTMLDivElement>
|
|
34
78
|
>(({ className, ...props }, ref) => (
|
|
35
|
-
<div ref={ref} className={
|
|
79
|
+
<div ref={ref} className={useCardClass("content", "p-6 pt-0", className)} {...props} />
|
|
36
80
|
));
|
|
37
81
|
CardContent.displayName = "CardContent";
|
|
38
82
|
|
|
39
|
-
|
|
83
|
+
const CardFooter = React.forwardRef<
|
|
84
|
+
HTMLDivElement,
|
|
85
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
86
|
+
>(({ className, ...props }, ref) => (
|
|
87
|
+
<div
|
|
88
|
+
ref={ref}
|
|
89
|
+
className={useCardClass("footer", "flex items-center p-6 pt-0", className)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
));
|
|
93
|
+
CardFooter.displayName = "CardFooter";
|
|
94
|
+
|
|
95
|
+
/** Top-right slot of a grid header (`grid-cols-[1fr_auto]`): spans the title and description rows. */
|
|
96
|
+
const CardAction = React.forwardRef<
|
|
97
|
+
HTMLDivElement,
|
|
98
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
99
|
+
>(({ className, ...props }, ref) => (
|
|
100
|
+
<div
|
|
101
|
+
ref={ref}
|
|
102
|
+
className={useCardClass("action", "col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)}
|
|
103
|
+
{...props}
|
|
104
|
+
/>
|
|
105
|
+
));
|
|
106
|
+
CardAction.displayName = "CardAction";
|
|
107
|
+
|
|
108
|
+
export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, CardAction };
|