@boyernick/standard-ui-react 0.1.1-canary.2 → 0.1.1-canary.22
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/package.json +5 -3
- package/src/accordion.tsx +1 -1
- package/src/alert-dialog.tsx +1 -1
- package/src/autocomplete.tsx +12 -11
- package/src/badge.tsx +73 -9
- package/src/brand.tsx +2 -2
- package/src/breadcrumb.tsx +38 -20
- package/src/button.tsx +2 -2
- package/src/calendar.tsx +22 -11
- package/src/card.tsx +57 -14
- package/src/carousel.tsx +163 -6
- package/src/checkbox.tsx +35 -13
- package/src/collapsible.tsx +4 -2
- package/src/combobox.tsx +13 -12
- package/src/command.tsx +38 -14
- package/src/context-menu.tsx +6 -3
- package/src/date-picker.tsx +174 -0
- package/src/dialog.tsx +33 -10
- package/src/drawer.tsx +1 -1
- package/src/empty.tsx +4 -4
- package/src/field.tsx +1 -1
- package/src/filter-group.tsx +139 -0
- package/src/icons.tsx +39 -0
- package/src/illustrations.tsx +219 -141
- package/src/image-modal.tsx +440 -49
- package/src/index.ts +188 -0
- package/src/input.tsx +1 -1
- package/src/kbd.tsx +49 -0
- package/src/lib/motion.ts +128 -18
- package/src/lib/popup.ts +45 -0
- package/src/menu.tsx +7 -4
- package/src/menubar.tsx +5 -0
- package/src/meter.tsx +1 -1
- package/src/minimap.tsx +267 -0
- package/src/navigation-menu.tsx +336 -67
- package/src/number-field.tsx +327 -61
- package/src/pagination.tsx +258 -42
- package/src/popover.tsx +3 -1
- package/src/preview-card.tsx +3 -1
- package/src/progress.tsx +6 -1
- package/src/questionnaire.tsx +279 -0
- package/src/scroll-area.tsx +1 -1
- package/src/select.tsx +8 -6
- package/src/sidebar.tsx +109 -28
- package/src/slider.tsx +64 -6
- package/src/sounds.tsx +6 -10
- package/src/spinner.tsx +105 -32
- package/src/switch.tsx +2 -2
- package/src/table.tsx +82 -15
- package/src/tabs.tsx +184 -38
- package/src/text-animate.tsx +71 -28
- package/src/textarea.tsx +1 -1
- package/src/timeline.tsx +321 -0
- package/src/toast.tsx +156 -23
- package/src/tooltip.tsx +43 -3
- package/src/video-player.tsx +296 -120
package/src/table.tsx
CHANGED
|
@@ -1,8 +1,60 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
1
2
|
import type { ComponentProps } from "react";
|
|
2
3
|
import { cn } from "./lib/cn";
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
const tableVariants = cva(
|
|
6
|
+
"group/table w-full text-sm text-fg-primary",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default: "border-collapse",
|
|
11
|
+
grid: [
|
|
12
|
+
"border-separate border-spacing-0",
|
|
13
|
+
"[&_tr]:border-b-0",
|
|
14
|
+
"[&_tr>*]:border-r [&_tr>*]:border-b [&_tr>*]:border-border-primary",
|
|
15
|
+
"[&_tr>*:last-child]:border-r-0",
|
|
16
|
+
"[&_tbody_tr:last-child>*]:border-b-0 [&_tfoot_tr:last-child>*]:border-b-0",
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
density: {
|
|
20
|
+
default: "",
|
|
21
|
+
compact: "[&_th]:h-8 [&_th]:px-3 [&_td]:px-3 [&_td]:py-2",
|
|
22
|
+
},
|
|
23
|
+
striped: {
|
|
24
|
+
true: "[&_tbody_tr:nth-child(even)]:bg-background-secondary",
|
|
25
|
+
false: "",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
defaultVariants: {
|
|
29
|
+
variant: "default",
|
|
30
|
+
density: "default",
|
|
31
|
+
striped: false,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const tableHeaderVariants = cva(
|
|
37
|
+
"bg-background-secondary [&_tr]:border-b [&_tr]:border-border-primary",
|
|
38
|
+
{
|
|
39
|
+
variants: {
|
|
40
|
+
sticky: {
|
|
41
|
+
true: "[&_th]:sticky [&_th]:top-0 [&_th]:z-10 [&_th]:bg-background-secondary [&_th]:py-2.5",
|
|
42
|
+
false: "",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
defaultVariants: {
|
|
46
|
+
sticky: false,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
export type TableProps = ComponentProps<"table"> &
|
|
52
|
+
VariantProps<typeof tableVariants> & {
|
|
53
|
+
/** Classes for the bordered horizontal-scroll container. */
|
|
54
|
+
containerClassName?: string;
|
|
55
|
+
};
|
|
56
|
+
export type TableHeaderProps = ComponentProps<"thead"> &
|
|
57
|
+
VariantProps<typeof tableHeaderVariants>;
|
|
6
58
|
export type TableBodyProps = ComponentProps<"tbody">;
|
|
7
59
|
export type TableFooterProps = ComponentProps<"tfoot">;
|
|
8
60
|
export type TableRowProps = ComponentProps<"tr">;
|
|
@@ -10,24 +62,37 @@ export type TableHeadProps = ComponentProps<"th">;
|
|
|
10
62
|
export type TableCellProps = ComponentProps<"td">;
|
|
11
63
|
export type TableCaptionProps = ComponentProps<"caption">;
|
|
12
64
|
|
|
13
|
-
export const Table = ({
|
|
14
|
-
|
|
65
|
+
export const Table = ({
|
|
66
|
+
className,
|
|
67
|
+
containerClassName,
|
|
68
|
+
variant,
|
|
69
|
+
density,
|
|
70
|
+
striped,
|
|
71
|
+
...props
|
|
72
|
+
}: TableProps) => (
|
|
73
|
+
<div
|
|
74
|
+
className={cn(
|
|
75
|
+
"w-full overflow-auto rounded-lg border border-border-primary bg-surface",
|
|
76
|
+
containerClassName,
|
|
77
|
+
)}
|
|
78
|
+
>
|
|
15
79
|
<table
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
)}
|
|
80
|
+
data-variant={variant ?? "default"}
|
|
81
|
+
data-density={density ?? "default"}
|
|
82
|
+
data-striped={striped || undefined}
|
|
83
|
+
className={cn(tableVariants({ variant, density, striped }), className)}
|
|
20
84
|
{...props}
|
|
21
85
|
/>
|
|
22
86
|
</div>
|
|
23
87
|
);
|
|
24
88
|
|
|
25
|
-
export const TableHeader = ({
|
|
89
|
+
export const TableHeader = ({
|
|
90
|
+
className,
|
|
91
|
+
sticky,
|
|
92
|
+
...props
|
|
93
|
+
}: TableHeaderProps) => (
|
|
26
94
|
<thead
|
|
27
|
-
className={cn(
|
|
28
|
-
"bg-background-secondary [&_tr]:border-b [&_tr]:border-border-primary",
|
|
29
|
-
className,
|
|
30
|
-
)}
|
|
95
|
+
className={cn(tableHeaderVariants({ sticky }), className)}
|
|
31
96
|
{...props}
|
|
32
97
|
/>
|
|
33
98
|
);
|
|
@@ -39,7 +104,7 @@ export const TableBody = ({ className, ...props }: TableBodyProps) => (
|
|
|
39
104
|
export const TableFooter = ({ className, ...props }: TableFooterProps) => (
|
|
40
105
|
<tfoot
|
|
41
106
|
className={cn(
|
|
42
|
-
"border-t border-border-primary bg-background-secondary font-medium",
|
|
107
|
+
"border-t border-border-primary bg-background-secondary font-medium [&_tr]:hover:bg-transparent",
|
|
43
108
|
className,
|
|
44
109
|
)}
|
|
45
110
|
{...props}
|
|
@@ -49,7 +114,7 @@ export const TableFooter = ({ className, ...props }: TableFooterProps) => (
|
|
|
49
114
|
export const TableRow = ({ className, ...props }: TableRowProps) => (
|
|
50
115
|
<tr
|
|
51
116
|
className={cn(
|
|
52
|
-
"border-b border-border-primary hover:bg-background-secondary",
|
|
117
|
+
"border-b border-border-primary transition-colors duration-[var(--duration-sm)] ease-enter hover:bg-background-secondary aria-selected:bg-background-tertiary data-[selected]:bg-background-tertiary data-[state=selected]:bg-background-tertiary",
|
|
53
118
|
className,
|
|
54
119
|
)}
|
|
55
120
|
{...props}
|
|
@@ -79,3 +144,5 @@ export const TableCaption = ({ className, ...props }: TableCaptionProps) => (
|
|
|
79
144
|
{...props}
|
|
80
145
|
/>
|
|
81
146
|
);
|
|
147
|
+
|
|
148
|
+
export { tableHeaderVariants, tableVariants };
|
package/src/tabs.tsx
CHANGED
|
@@ -1,62 +1,208 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import { Tabs as BaseTabs } from "@base-ui/react/tabs"
|
|
4
|
-
import type
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
5
|
+
import { createContext, type ComponentProps, useContext } from "react"
|
|
5
6
|
import { cn } from "./lib/cn"
|
|
6
7
|
import { motion } from "./lib/motion"
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
const tabsListVariants = cva("relative flex items-center", {
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
underline:
|
|
13
|
+
"w-full gap-0.5 border-b border-border-primary data-[orientation=vertical]:w-40 data-[orientation=vertical]:shrink-0 data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch data-[orientation=vertical]:gap-0 data-[orientation=vertical]:border-r data-[orientation=vertical]:border-b-0",
|
|
14
|
+
segmented:
|
|
15
|
+
"w-fit rounded-lg bg-background-tertiary p-0.5 data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch",
|
|
16
|
+
pill: "w-fit gap-1 data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
defaultVariants: {
|
|
20
|
+
variant: "underline",
|
|
21
|
+
},
|
|
22
|
+
})
|
|
13
23
|
|
|
14
|
-
|
|
15
|
-
|
|
24
|
+
const tabsTabVariants = cva(
|
|
25
|
+
"relative z-10 inline-flex shrink-0 cursor-pointer items-center justify-center gap-1.5 whitespace-nowrap text-fg-tertiary outline-none select-none hover:text-fg-primary focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-background-primary focus-visible:ring-ring/20 data-active:text-fg-primary data-disabled:cursor-not-allowed data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
26
|
+
{
|
|
27
|
+
variants: {
|
|
28
|
+
variant: {
|
|
29
|
+
underline:
|
|
30
|
+
"data-[orientation=horizontal]:pb-1 data-[orientation=vertical]:w-full data-[orientation=vertical]:justify-start",
|
|
31
|
+
segmented: "rounded-md",
|
|
32
|
+
pill: "rounded-full bg-background-tertiary data-active:bg-transparent",
|
|
33
|
+
},
|
|
34
|
+
size: {
|
|
35
|
+
sm: "text-xs [&_svg]:size-3.5",
|
|
36
|
+
md: "text-sm [&_svg]:size-4",
|
|
37
|
+
lg: "text-md [&_svg]:size-4",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
compoundVariants: [
|
|
41
|
+
{
|
|
42
|
+
variant: "underline",
|
|
43
|
+
size: "sm",
|
|
44
|
+
class: "h-8 px-2.5 data-[orientation=horizontal]:h-9",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
variant: "underline",
|
|
48
|
+
size: "md",
|
|
49
|
+
class: "h-10 px-3 data-[orientation=horizontal]:h-11",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
variant: "underline",
|
|
53
|
+
size: "lg",
|
|
54
|
+
class: "h-12 px-3.5 data-[orientation=horizontal]:h-13",
|
|
55
|
+
},
|
|
56
|
+
{ variant: "segmented", size: "sm", class: "h-7 px-2.5" },
|
|
57
|
+
{ variant: "segmented", size: "md", class: "h-8 px-3" },
|
|
58
|
+
{ variant: "segmented", size: "lg", class: "h-9 px-3.5" },
|
|
59
|
+
{ variant: "pill", size: "sm", class: "h-7 px-2.5" },
|
|
60
|
+
{ variant: "pill", size: "md", class: "h-8 px-3" },
|
|
61
|
+
{ variant: "pill", size: "lg", class: "h-9 px-3.5" },
|
|
62
|
+
],
|
|
63
|
+
defaultVariants: {
|
|
64
|
+
variant: "underline",
|
|
65
|
+
size: "md",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
16
68
|
)
|
|
17
69
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
70
|
+
const tabsIndicatorVariants = cva("absolute z-0", {
|
|
71
|
+
variants: {
|
|
72
|
+
variant: {
|
|
73
|
+
underline:
|
|
74
|
+
"bottom-0 left-0 h-0.5 w-(--active-tab-width) translate-x-(--active-tab-left) rounded-full bg-brand-primary data-[orientation=vertical]:top-0 data-[orientation=vertical]:right-0 data-[orientation=vertical]:bottom-auto data-[orientation=vertical]:left-auto data-[orientation=vertical]:h-(--active-tab-height) data-[orientation=vertical]:w-0.5 data-[orientation=vertical]:translate-x-0 data-[orientation=vertical]:translate-y-(--active-tab-top)",
|
|
75
|
+
segmented:
|
|
76
|
+
"inset-y-0.5 left-0 w-(--active-tab-width) translate-x-(--active-tab-left) rounded-md bg-surface-raised shadow-hairline data-[orientation=vertical]:inset-x-0.5 data-[orientation=vertical]:top-0 data-[orientation=vertical]:bottom-auto data-[orientation=vertical]:h-(--active-tab-height) data-[orientation=vertical]:w-auto data-[orientation=vertical]:translate-x-0 data-[orientation=vertical]:translate-y-(--active-tab-top)",
|
|
77
|
+
pill: "inset-y-0 left-0 w-(--active-tab-width) translate-x-(--active-tab-left) rounded-full bg-surface-raised shadow-hairline data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:top-0 data-[orientation=vertical]:bottom-auto data-[orientation=vertical]:h-(--active-tab-height) data-[orientation=vertical]:w-auto data-[orientation=vertical]:translate-x-0 data-[orientation=vertical]:translate-y-(--active-tab-top)",
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
defaultVariants: {
|
|
81
|
+
variant: "underline",
|
|
82
|
+
},
|
|
83
|
+
})
|
|
27
84
|
|
|
28
|
-
export
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
85
|
+
export type TabsVariant = NonNullable<
|
|
86
|
+
VariantProps<typeof tabsListVariants>["variant"]
|
|
87
|
+
>
|
|
88
|
+
export type TabsSize = NonNullable<
|
|
89
|
+
VariantProps<typeof tabsTabVariants>["size"]
|
|
90
|
+
>
|
|
91
|
+
|
|
92
|
+
type TabsStyleContextValue = {
|
|
93
|
+
variant: TabsVariant
|
|
94
|
+
size: TabsSize
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const TabsStyleContext = createContext<TabsStyleContextValue>({
|
|
98
|
+
variant: "underline",
|
|
99
|
+
size: "md",
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
export type TabsProps = ComponentProps<typeof BaseTabs.Root> & {
|
|
103
|
+
/** Visual treatment shared by the list, tabs, and indicator. */
|
|
104
|
+
variant?: TabsVariant
|
|
105
|
+
/** Height and typography shared by every tab. */
|
|
106
|
+
size?: TabsSize
|
|
107
|
+
}
|
|
108
|
+
export type TabsListProps = ComponentProps<typeof BaseTabs.List> &
|
|
109
|
+
VariantProps<typeof tabsListVariants>
|
|
110
|
+
export type TabsTabProps = ComponentProps<typeof BaseTabs.Tab> &
|
|
111
|
+
VariantProps<typeof tabsTabVariants>
|
|
112
|
+
export type TabsIndicatorProps = ComponentProps<typeof BaseTabs.Indicator> &
|
|
113
|
+
VariantProps<typeof tabsIndicatorVariants>
|
|
114
|
+
export type TabsPanelProps = ComponentProps<typeof BaseTabs.Panel>
|
|
115
|
+
|
|
116
|
+
export const Tabs = ({
|
|
117
|
+
className,
|
|
118
|
+
variant = "underline",
|
|
119
|
+
size = "md",
|
|
120
|
+
...props
|
|
121
|
+
}: TabsProps) => (
|
|
122
|
+
<TabsStyleContext.Provider value={{ variant, size }}>
|
|
123
|
+
<BaseTabs.Root
|
|
124
|
+
data-slot="tabs"
|
|
125
|
+
className={cn(
|
|
126
|
+
"flex w-full flex-col data-[orientation=vertical]:flex-row data-[orientation=vertical]:gap-6",
|
|
127
|
+
className,
|
|
128
|
+
)}
|
|
129
|
+
{...props}
|
|
130
|
+
/>
|
|
131
|
+
</TabsStyleContext.Provider>
|
|
38
132
|
)
|
|
39
133
|
|
|
134
|
+
export const TabsList = ({
|
|
135
|
+
className,
|
|
136
|
+
variant,
|
|
137
|
+
...props
|
|
138
|
+
}: TabsListProps) => {
|
|
139
|
+
const styles = useContext(TabsStyleContext)
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<BaseTabs.List
|
|
143
|
+
data-slot="tabs-list"
|
|
144
|
+
className={cn(
|
|
145
|
+
tabsListVariants({ variant: variant ?? styles.variant }),
|
|
146
|
+
className,
|
|
147
|
+
)}
|
|
148
|
+
{...props}
|
|
149
|
+
/>
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export const TabsTab = ({
|
|
154
|
+
className,
|
|
155
|
+
variant,
|
|
156
|
+
size,
|
|
157
|
+
...props
|
|
158
|
+
}: TabsTabProps) => {
|
|
159
|
+
const styles = useContext(TabsStyleContext)
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<BaseTabs.Tab
|
|
163
|
+
data-slot="tabs-tab"
|
|
164
|
+
className={cn(
|
|
165
|
+
tabsTabVariants({
|
|
166
|
+
variant: variant ?? styles.variant,
|
|
167
|
+
size: size ?? styles.size,
|
|
168
|
+
}),
|
|
169
|
+
motion.colors,
|
|
170
|
+
className,
|
|
171
|
+
)}
|
|
172
|
+
{...props}
|
|
173
|
+
/>
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
40
177
|
export const TabsIndicator = ({
|
|
41
178
|
className,
|
|
179
|
+
variant,
|
|
42
180
|
...props
|
|
43
|
-
}: TabsIndicatorProps) =>
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
181
|
+
}: TabsIndicatorProps) => {
|
|
182
|
+
const styles = useContext(TabsStyleContext)
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<BaseTabs.Indicator
|
|
186
|
+
data-slot="tabs-indicator"
|
|
187
|
+
className={cn(
|
|
188
|
+
tabsIndicatorVariants({ variant: variant ?? styles.variant }),
|
|
189
|
+
motion.tabsIndicator,
|
|
190
|
+
className,
|
|
191
|
+
)}
|
|
192
|
+
{...props}
|
|
193
|
+
/>
|
|
194
|
+
)
|
|
195
|
+
}
|
|
53
196
|
|
|
54
197
|
export const TabsPanel = ({ className, ...props }: TabsPanelProps) => (
|
|
55
198
|
<BaseTabs.Panel
|
|
199
|
+
data-slot="tabs-panel"
|
|
56
200
|
className={cn(
|
|
57
|
-
"pt-4 text-sm leading-relaxed text-fg-secondary outline-none focus-visible:outline-none",
|
|
201
|
+
"pt-4 text-sm leading-relaxed text-fg-secondary outline-none focus-visible:outline-none data-[orientation=vertical]:pt-0",
|
|
58
202
|
className,
|
|
59
203
|
)}
|
|
60
204
|
{...props}
|
|
61
205
|
/>
|
|
62
206
|
)
|
|
207
|
+
|
|
208
|
+
export { tabsIndicatorVariants, tabsListVariants, tabsTabVariants }
|
package/src/text-animate.tsx
CHANGED
|
@@ -8,12 +8,18 @@ import {
|
|
|
8
8
|
} from "react"
|
|
9
9
|
import { cn } from "./lib/cn"
|
|
10
10
|
|
|
11
|
+
/** How many glyphs are scrambling at once. The window travels the line; behind
|
|
12
|
+
* it the text is settled, ahead of it nothing is drawn yet. 8 is the reference
|
|
13
|
+
* default from baffle.js, which this effect follows. */
|
|
14
|
+
const DECODE_WINDOW = 8
|
|
15
|
+
|
|
11
16
|
const DECODE_GLYPHS =
|
|
12
17
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789#@$%&*"
|
|
13
18
|
|
|
14
19
|
export type TextAnimateEffect =
|
|
15
20
|
| "typewriter"
|
|
16
21
|
| "decode"
|
|
22
|
+
| "shimmer"
|
|
17
23
|
| "fade"
|
|
18
24
|
| "blur"
|
|
19
25
|
|
|
@@ -32,20 +38,25 @@ export type TextAnimateProps = HTMLAttributes<HTMLSpanElement> & {
|
|
|
32
38
|
export const TextAnimate = ({
|
|
33
39
|
text,
|
|
34
40
|
effect = "typewriter",
|
|
35
|
-
speed =
|
|
41
|
+
speed = 60,
|
|
36
42
|
delay = 0,
|
|
37
43
|
replay = true,
|
|
38
44
|
as: Tag = "span",
|
|
39
45
|
className,
|
|
40
46
|
...props
|
|
41
47
|
}: TextAnimateProps) => {
|
|
42
|
-
const [output, setOutput] = useState(
|
|
43
|
-
|
|
48
|
+
const [output, setOutput] = useState(
|
|
49
|
+
effect === "typewriter" || effect === "decode" ? "" : text,
|
|
50
|
+
)
|
|
51
|
+
const [done, setDone] = useState(
|
|
52
|
+
effect === "fade" || effect === "blur" || effect === "shimmer",
|
|
53
|
+
)
|
|
44
54
|
const chars = useMemo(() => [...text], [text])
|
|
45
55
|
|
|
46
56
|
useEffect(() => {
|
|
47
57
|
let cancelled = false
|
|
48
58
|
let timeoutId: ReturnType<typeof setTimeout> | undefined
|
|
59
|
+
let frameId: number | undefined
|
|
49
60
|
|
|
50
61
|
const start = () => {
|
|
51
62
|
if (
|
|
@@ -57,7 +68,7 @@ export const TextAnimate = ({
|
|
|
57
68
|
return
|
|
58
69
|
}
|
|
59
70
|
|
|
60
|
-
if (effect === "fade" || effect === "blur") {
|
|
71
|
+
if (effect === "fade" || effect === "blur" || effect === "shimmer") {
|
|
61
72
|
setOutput(text)
|
|
62
73
|
setDone(true)
|
|
63
74
|
return
|
|
@@ -82,30 +93,59 @@ export const TextAnimate = ({
|
|
|
82
93
|
return
|
|
83
94
|
}
|
|
84
95
|
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
96
|
+
// Decode follows baffle.js's `reveal`, which is the reference for this
|
|
97
|
+
// effect: a fixed-width window of scrambled glyphs travels left to
|
|
98
|
+
// right, the real text sits behind it, and nothing at all is drawn ahead
|
|
99
|
+
// of it — so the line grows in with a churning leading edge rather than
|
|
100
|
+
// resolving out of a full-width block of noise.
|
|
101
|
+
//
|
|
102
|
+
// It runs on rAF with a time accumulator rather than `setTimeout(speed)`.
|
|
103
|
+
// A 28ms timer does not divide into a ~16.7ms frame, so steps land on
|
|
104
|
+
// uneven frames and the text visibly stutters; accumulating elapsed time
|
|
105
|
+
// keeps the reference's cadence while landing each step on a real frame.
|
|
106
|
+
const slots = chars.reduce<number[]>((acc, char, index) => {
|
|
107
|
+
if (char !== " ") acc.push(index)
|
|
108
|
+
return acc
|
|
109
|
+
}, [])
|
|
110
|
+
|
|
111
|
+
// The window starts off the front of the line so the first glyph is
|
|
112
|
+
// already scrambling as it arrives.
|
|
113
|
+
let position = -DECODE_WINDOW
|
|
114
|
+
let previousStep = -Infinity
|
|
115
|
+
const startedAt = performance.now()
|
|
116
|
+
|
|
117
|
+
const frame = (now: number) => {
|
|
91
118
|
if (cancelled) return
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
119
|
+
|
|
120
|
+
const step = Math.floor((now - startedAt) / speed) - DECODE_WINDOW
|
|
121
|
+
if (step !== previousStep) {
|
|
122
|
+
previousStep = step
|
|
123
|
+
position = step
|
|
124
|
+
|
|
125
|
+
if (position > slots.length) {
|
|
126
|
+
setOutput(text)
|
|
127
|
+
setDone(true)
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const next = [...chars]
|
|
132
|
+
for (let p = Math.max(position, 0); p < slots.length; p += 1) {
|
|
133
|
+
next[slots[p]] =
|
|
134
|
+
p < position + DECODE_WINDOW
|
|
135
|
+
? DECODE_GLYPHS[
|
|
136
|
+
Math.floor(Math.random() * DECODE_GLYPHS.length)
|
|
137
|
+
]
|
|
138
|
+
: ""
|
|
139
|
+
}
|
|
140
|
+
setOutput(next.join(""))
|
|
106
141
|
}
|
|
142
|
+
|
|
143
|
+
frameId = requestAnimationFrame(frame)
|
|
107
144
|
}
|
|
108
|
-
|
|
145
|
+
|
|
146
|
+
setOutput("")
|
|
147
|
+
frameId = requestAnimationFrame(frame)
|
|
148
|
+
return
|
|
109
149
|
}
|
|
110
150
|
|
|
111
151
|
timeoutId = setTimeout(start, delay)
|
|
@@ -113,18 +153,21 @@ export const TextAnimate = ({
|
|
|
113
153
|
return () => {
|
|
114
154
|
cancelled = true
|
|
115
155
|
if (timeoutId) clearTimeout(timeoutId)
|
|
156
|
+
if (frameId) cancelAnimationFrame(frameId)
|
|
116
157
|
}
|
|
117
158
|
}, [chars, delay, effect, replay, speed, text])
|
|
118
159
|
|
|
119
|
-
if (effect === "fade" || effect === "blur") {
|
|
160
|
+
if (effect === "fade" || effect === "blur" || effect === "shimmer") {
|
|
120
161
|
return (
|
|
121
162
|
<Tag
|
|
122
163
|
className={cn(
|
|
123
164
|
"inline-block text-fg-primary",
|
|
124
165
|
effect === "fade" &&
|
|
125
|
-
"animate-[text-fade-in_0.
|
|
166
|
+
"animate-[text-fade-in_0.9s_var(--ease-enter)_both] motion-reduce:animate-none",
|
|
126
167
|
effect === "blur" &&
|
|
127
|
-
"animate-[text-blur-
|
|
168
|
+
"animate-[text-blur-in_1s_var(--ease-enter)_both] motion-reduce:animate-none",
|
|
169
|
+
effect === "shimmer" &&
|
|
170
|
+
"bg-[linear-gradient(110deg,var(--color-fg-tertiary)_35%,var(--color-fg-primary)_50%,var(--color-fg-tertiary)_65%)] bg-[length:250%_100%] bg-clip-text text-transparent animate-[text-shimmer_3s_linear_infinite] motion-reduce:animate-none motion-reduce:bg-none motion-reduce:text-fg-primary",
|
|
128
171
|
className,
|
|
129
172
|
)}
|
|
130
173
|
style={{ animationDelay: `${delay}ms` }}
|
package/src/textarea.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import type { TextareaHTMLAttributes } from "react"
|
|
|
3
3
|
import { cn } from "./lib/cn"
|
|
4
4
|
|
|
5
5
|
const textareaVariants = cva(
|
|
6
|
-
"flex min-h-20 w-full cursor-text resize-y rounded-md px-3 py-2 text-sm text-fg-primary transition-[color,box-shadow] duration-
|
|
6
|
+
"flex min-h-20 w-full cursor-text resize-y rounded-md px-3 py-2 text-sm text-fg-primary transition-[color,box-shadow] duration-[var(--duration-sm)] ease-enter placeholder:text-fg-quaternary outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-background-primary focus-visible:ring-ring/20 aria-invalid:border-destructive aria-invalid:focus-visible:border-destructive aria-invalid:focus-visible:ring-destructive/20 disabled:cursor-not-allowed disabled:opacity-50",
|
|
7
7
|
{
|
|
8
8
|
variants: {
|
|
9
9
|
variant: {
|