@boyernick/standard-ui-react 0.1.0 → 0.1.1-canary.10
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 +9 -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/breadcrumb.tsx +39 -21
- package/src/button.tsx +2 -2
- package/src/calendar.tsx +22 -11
- package/src/card.tsx +57 -14
- package/src/carousel.tsx +191 -25
- package/src/checkbox.tsx +35 -13
- package/src/collapsible.tsx +4 -2
- package/src/combobox.tsx +13 -12
- package/src/command.tsx +24 -12
- package/src/context-menu.tsx +6 -3
- package/src/date-picker.tsx +174 -0
- package/src/dialog.tsx +1 -1
- package/src/drawer.tsx +1 -1
- package/src/empty.tsx +4 -4
- package/src/field.tsx +1 -1
- package/src/icons.tsx +27 -0
- package/src/illustrations.tsx +219 -141
- package/src/index.ts +29 -0
- package/src/input.tsx +1 -1
- package/src/kbd.tsx +43 -0
- package/src/lib/cn.ts +43 -1
- package/src/lib/motion.ts +128 -18
- package/src/lib/popup.ts +45 -0
- package/src/markdown-editor.tsx +1 -1
- package/src/menu.tsx +7 -4
- package/src/meter.tsx +1 -1
- package/src/navigation-menu.tsx +5 -3
- package/src/popover.tsx +3 -1
- package/src/preview-card.tsx +3 -1
- package/src/progress.tsx +1 -1
- package/src/scroll-area.tsx +1 -1
- package/src/select.tsx +8 -6
- package/src/slider.tsx +1 -1
- package/src/switch.tsx +2 -2
- package/src/text-animate.tsx +2 -4
- package/src/textarea.tsx +1 -1
- package/src/toast.tsx +2 -2
package/src/illustrations.tsx
CHANGED
|
@@ -1,167 +1,245 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ComponentProps, ReactNode } from "react"
|
|
2
2
|
import { cn } from "./lib/cn"
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Small, composition-based illustrations for empty states.
|
|
6
|
+
*
|
|
7
|
+
* Built from divs and theme tokens rather than SVG paths, so they follow the
|
|
8
|
+
* palette into dark mode instead of carrying baked-in colours. They are
|
|
9
|
+
* deliberately abstract and monochrome — a miniature of the thing that is
|
|
10
|
+
* missing, not a picture about it. Nothing here uses a brand colour: an empty
|
|
11
|
+
* state is a quiet moment, and an accent turns it into an announcement.
|
|
12
|
+
*/
|
|
5
13
|
|
|
6
|
-
|
|
7
|
-
viewBox: "0 0 240 160",
|
|
8
|
-
fill: "none",
|
|
9
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
10
|
-
} as const
|
|
14
|
+
export type IllustrationProps = ComponentProps<"div">
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
/** Fixed 64×48 stage, so a row of these lines up without ad-hoc sizing. */
|
|
17
|
+
const Frame = ({ className, children, ...props }: IllustrationProps) => (
|
|
18
|
+
<div
|
|
19
|
+
className={cn(
|
|
20
|
+
"inline-flex h-12 w-16 shrink-0 items-center justify-center",
|
|
21
|
+
className,
|
|
22
|
+
)}
|
|
19
23
|
aria-hidden
|
|
20
24
|
{...props}
|
|
21
25
|
>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
className="fill-background-secondary"
|
|
25
|
-
stroke="currentColor"
|
|
26
|
-
strokeWidth="2"
|
|
27
|
-
/>
|
|
28
|
-
<path
|
|
29
|
-
d="M52 65h45l8 13h30l8-13h45"
|
|
30
|
-
stroke="currentColor"
|
|
31
|
-
strokeWidth="2"
|
|
32
|
-
strokeLinejoin="round"
|
|
33
|
-
/>
|
|
34
|
-
<path
|
|
35
|
-
d="M97 31h46"
|
|
36
|
-
className="text-brand-primary"
|
|
37
|
-
stroke="currentColor"
|
|
38
|
-
strokeWidth="4"
|
|
39
|
-
strokeLinecap="round"
|
|
40
|
-
/>
|
|
41
|
-
<circle cx="120" cy="104" r="4" className="fill-brand-primary" />
|
|
42
|
-
</svg>
|
|
26
|
+
{children}
|
|
27
|
+
</div>
|
|
43
28
|
)
|
|
44
29
|
|
|
45
|
-
|
|
30
|
+
/** A sheet. The hairline and radius are sub-pixel on purpose — at this size a
|
|
31
|
+
* 1px border reads as a heavy outline rather than an edge. */
|
|
32
|
+
const Card = ({
|
|
33
|
+
className,
|
|
34
|
+
children,
|
|
35
|
+
}: {
|
|
36
|
+
className?: string
|
|
37
|
+
children?: ReactNode
|
|
38
|
+
}) => (
|
|
39
|
+
<div
|
|
40
|
+
className={cn(
|
|
41
|
+
"relative flex shrink-0 flex-col overflow-hidden rounded-[3px] border-[0.5px] border-border-primary bg-surface p-1 shadow-hairline",
|
|
42
|
+
className,
|
|
43
|
+
)}
|
|
44
|
+
>
|
|
45
|
+
{children}
|
|
46
|
+
</div>
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
/** A line of content. */
|
|
50
|
+
const Bar = ({ width }: { width: string }) => (
|
|
51
|
+
<div
|
|
52
|
+
className="h-[3px] shrink-0 rounded-[1px] bg-background-tertiary"
|
|
53
|
+
style={{ width }}
|
|
54
|
+
/>
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
/** Nothing here yet — a sheet with no content on it. */
|
|
58
|
+
export const IllustrationEmpty = ({ className, ...props }: IllustrationProps) => (
|
|
59
|
+
<Frame className={className} {...props}>
|
|
60
|
+
<Card className="h-10 w-8" />
|
|
61
|
+
</Frame>
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
/** Something to look through — a sheet behind a lens. */
|
|
65
|
+
export const IllustrationSearch = ({
|
|
46
66
|
className,
|
|
47
67
|
...props
|
|
48
68
|
}: IllustrationProps) => (
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
y="28"
|
|
58
|
-
width="146"
|
|
59
|
-
height="104"
|
|
60
|
-
rx="16"
|
|
61
|
-
className="fill-background-secondary"
|
|
62
|
-
stroke="currentColor"
|
|
63
|
-
strokeWidth="2"
|
|
64
|
-
/>
|
|
65
|
-
<path
|
|
66
|
-
d="M47 54h146"
|
|
67
|
-
stroke="currentColor"
|
|
68
|
-
strokeWidth="2"
|
|
69
|
-
/>
|
|
70
|
-
<circle cx="63" cy="41" r="3" className="fill-brand-primary" />
|
|
71
|
-
<circle cx="74" cy="41" r="3" fill="currentColor" opacity=".45" />
|
|
72
|
-
<circle cx="85" cy="41" r="3" fill="currentColor" opacity=".25" />
|
|
73
|
-
<path
|
|
74
|
-
d="m103 78 34 34m0-34-34 34"
|
|
75
|
-
className="text-brand-primary"
|
|
76
|
-
stroke="currentColor"
|
|
77
|
-
strokeWidth="5"
|
|
78
|
-
strokeLinecap="round"
|
|
79
|
-
/>
|
|
80
|
-
</svg>
|
|
69
|
+
<Frame className={className} {...props}>
|
|
70
|
+
<Card className="h-10 w-8 gap-[3px]">
|
|
71
|
+
<Bar width="80%" />
|
|
72
|
+
<Bar width="55%" />
|
|
73
|
+
<Bar width="35%" />
|
|
74
|
+
</Card>
|
|
75
|
+
<div className="-ml-2.5 mt-4 size-4 rounded-full border-[1.5px] border-border-secondary bg-surface" />
|
|
76
|
+
</Frame>
|
|
81
77
|
)
|
|
82
78
|
|
|
83
|
-
|
|
79
|
+
/** Something went wrong — the stack knocked out of true. */
|
|
80
|
+
export const IllustrationError = ({ className, ...props }: IllustrationProps) => (
|
|
81
|
+
<Frame className={className} {...props}>
|
|
82
|
+
<Card className="h-9 w-7 -rotate-6" />
|
|
83
|
+
<Card className="-ml-3 h-10 w-8 rotate-[7deg] gap-[3px]">
|
|
84
|
+
<Bar width="70%" />
|
|
85
|
+
<Bar width="45%" />
|
|
86
|
+
</Card>
|
|
87
|
+
</Frame>
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
/** Somewhere to put a file — a sheet going into a folder.
|
|
91
|
+
*
|
|
92
|
+
* Two earlier attempts failed for the same reason: an arrow stamped on a
|
|
93
|
+
* sheet, then a sheet on a plain grey slab, both read as a document with
|
|
94
|
+
* something next to it. A folder is its own silhouette — tab, body, a sheet
|
|
95
|
+
* tucked behind the front — so the arrangement carries the meaning and no
|
|
96
|
+
* symbol has to. It is also the one shape here that is not another sheet,
|
|
97
|
+
* which keeps it from reading as the error stack. */
|
|
98
|
+
export const IllustrationUpload = ({
|
|
84
99
|
className,
|
|
85
100
|
...props
|
|
86
101
|
}: IllustrationProps) => (
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
strokeWidth="2"
|
|
100
|
-
/>
|
|
101
|
-
<circle
|
|
102
|
-
cx="120"
|
|
103
|
-
cy="80"
|
|
104
|
-
r="37"
|
|
105
|
-
className="fill-brand-primary text-brand-primary"
|
|
106
|
-
stroke="currentColor"
|
|
107
|
-
strokeWidth="2"
|
|
108
|
-
/>
|
|
109
|
-
<path
|
|
110
|
-
d="m99 80 14 14 29-31"
|
|
111
|
-
className="text-brand-foreground"
|
|
112
|
-
stroke="currentColor"
|
|
113
|
-
strokeWidth="6"
|
|
114
|
-
strokeLinecap="round"
|
|
115
|
-
strokeLinejoin="round"
|
|
116
|
-
/>
|
|
117
|
-
<path
|
|
118
|
-
d="M63 42h13M69.5 35.5v13M168 118h13m-6.5-6.5v13"
|
|
119
|
-
className="text-brand-primary"
|
|
120
|
-
stroke="currentColor"
|
|
121
|
-
strokeWidth="3"
|
|
122
|
-
strokeLinecap="round"
|
|
123
|
-
/>
|
|
124
|
-
</svg>
|
|
102
|
+
<Frame className={className} {...props}>
|
|
103
|
+
<div className="relative h-11 w-12">
|
|
104
|
+
{/* Tucked deep enough that the folder front cuts across it. */}
|
|
105
|
+
<Card className="absolute inset-x-[13px] top-0 h-8 -rotate-6 gap-[3px]">
|
|
106
|
+
<Bar width="70%" />
|
|
107
|
+
<Bar width="45%" />
|
|
108
|
+
</Card>
|
|
109
|
+
{/* Laps 1px into the body so their borders read as one outline. */}
|
|
110
|
+
<div className="absolute bottom-[21px] left-1 h-1.5 w-4 rounded-t-[2px] border-x-[0.5px] border-t-[0.5px] border-border-secondary bg-background-tertiary" />
|
|
111
|
+
<div className="absolute inset-x-1 bottom-0 h-[22px] rounded-[3px] border-[0.5px] border-border-secondary bg-background-tertiary" />
|
|
112
|
+
</div>
|
|
113
|
+
</Frame>
|
|
125
114
|
)
|
|
126
115
|
|
|
127
|
-
|
|
116
|
+
/** Done — a sheet carrying a mark. */
|
|
117
|
+
export const IllustrationSuccess = ({
|
|
128
118
|
className,
|
|
129
119
|
...props
|
|
130
120
|
}: IllustrationProps) => (
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
|
|
121
|
+
<Frame className={className} {...props}>
|
|
122
|
+
<Card className="h-10 w-8 items-center justify-center">
|
|
123
|
+
{/* A check is cheaper as two borders on a rotated box than as a path. */}
|
|
124
|
+
<span className="mb-0.5 h-3 w-1.5 rotate-45 border-r-[1.5px] border-b-[1.5px] border-fg-tertiary" />
|
|
125
|
+
</Card>
|
|
126
|
+
</Frame>
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
/* -------------------------------------------------------------------------
|
|
130
|
+
* File types
|
|
131
|
+
*
|
|
132
|
+
* A second, smaller family. These sit inline — in an attachment row, a list,
|
|
133
|
+
* a preview slot — so they are a bare sheet at 24×30 rather than a hero on a
|
|
134
|
+
* 64×48 stage. Same grammar: a sheet, and marks on it standing in for whatever
|
|
135
|
+
* the file holds.
|
|
136
|
+
*
|
|
137
|
+
* Presentational, like Kbd: they render the type you name. Mapping an
|
|
138
|
+
* extension to a type is the consumer's job, since only the consumer knows
|
|
139
|
+
* what it wants to do with the ones it does not recognise.
|
|
140
|
+
* ---------------------------------------------------------------------- */
|
|
141
|
+
|
|
142
|
+
export type FileIllustrationType =
|
|
143
|
+
| "document"
|
|
144
|
+
| "image"
|
|
145
|
+
| "spreadsheet"
|
|
146
|
+
| "presentation"
|
|
147
|
+
| "video"
|
|
148
|
+
| "audio"
|
|
149
|
+
| "code"
|
|
150
|
+
| "archive"
|
|
151
|
+
| "generic"
|
|
152
|
+
|
|
153
|
+
export type IllustrationFileProps = IllustrationProps & {
|
|
154
|
+
type?: FileIllustrationType
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** A block of colour standing in for content rather than a line of it. */
|
|
158
|
+
const Block = ({ className }: { className?: string }) => (
|
|
159
|
+
<div className={cn("rounded-[1px] bg-background-tertiary", className)} />
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
const marks: Record<FileIllustrationType, ReactNode> = {
|
|
163
|
+
document: (
|
|
164
|
+
<>
|
|
165
|
+
<Bar width="100%" />
|
|
166
|
+
<Bar width="100%" />
|
|
167
|
+
<Bar width="60%" />
|
|
168
|
+
</>
|
|
169
|
+
),
|
|
170
|
+
// A horizon and a sun — the smallest arrangement that reads as a picture.
|
|
171
|
+
image: (
|
|
172
|
+
<div className="relative mt-auto h-3.5 w-full">
|
|
173
|
+
<Block className="absolute top-0 right-1 size-1.5 rounded-full" />
|
|
174
|
+
<Block className="absolute inset-x-0 bottom-0 h-2 [clip-path:polygon(0_100%,38%_28%,62%_100%)]" />
|
|
175
|
+
<Block className="absolute right-0 bottom-0 h-1.5 w-1/2 [clip-path:polygon(0_100%,50%_20%,100%_100%)]" />
|
|
176
|
+
</div>
|
|
177
|
+
),
|
|
178
|
+
spreadsheet: (
|
|
179
|
+
<div className="grid h-full w-full grid-cols-3 grid-rows-3 gap-[1.5px]">
|
|
180
|
+
{Array.from({ length: 9 }, (_, i) => (
|
|
181
|
+
<Block key={i} />
|
|
182
|
+
))}
|
|
183
|
+
</div>
|
|
184
|
+
),
|
|
185
|
+
presentation: (
|
|
186
|
+
<>
|
|
187
|
+
<Block className="h-2.5 w-full" />
|
|
188
|
+
<Bar width="70%" />
|
|
189
|
+
</>
|
|
190
|
+
),
|
|
191
|
+
// A play triangle, cut from a block so it keeps the same fill as everything else.
|
|
192
|
+
video: (
|
|
193
|
+
<div className="m-auto size-3">
|
|
194
|
+
<Block className="size-full [clip-path:polygon(22%_10%,90%_50%,22%_90%)]" />
|
|
195
|
+
</div>
|
|
196
|
+
),
|
|
197
|
+
audio: (
|
|
198
|
+
<div className="m-auto flex h-3.5 items-center gap-[2px]">
|
|
199
|
+
<Block className="h-1.5 w-[2px]" />
|
|
200
|
+
<Block className="h-3.5 w-[2px]" />
|
|
201
|
+
<Block className="h-2.5 w-[2px]" />
|
|
202
|
+
<Block className="h-3.5 w-[2px]" />
|
|
203
|
+
<Block className="h-1.5 w-[2px]" />
|
|
204
|
+
</div>
|
|
205
|
+
),
|
|
206
|
+
code: (
|
|
207
|
+
<>
|
|
208
|
+
<Bar width="55%" />
|
|
209
|
+
<div className="flex w-full pl-1.5">
|
|
210
|
+
<Bar width="70%" />
|
|
211
|
+
</div>
|
|
212
|
+
<div className="flex w-full pl-1.5">
|
|
213
|
+
<Bar width="50%" />
|
|
214
|
+
</div>
|
|
215
|
+
<Bar width="40%" />
|
|
216
|
+
</>
|
|
217
|
+
),
|
|
218
|
+
// A stack, since an archive is other files in a coat.
|
|
219
|
+
archive: (
|
|
220
|
+
<div className="m-auto flex flex-col gap-[2px]">
|
|
221
|
+
<Block className="h-1.5 w-2.5" />
|
|
222
|
+
<Block className="h-1.5 w-2.5" />
|
|
223
|
+
<Block className="h-1.5 w-2.5" />
|
|
224
|
+
</div>
|
|
225
|
+
),
|
|
226
|
+
generic: null,
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** One file, drawn as the kind of thing it holds. */
|
|
230
|
+
export const IllustrationFile = ({
|
|
231
|
+
type = "generic",
|
|
232
|
+
className,
|
|
233
|
+
...props
|
|
234
|
+
}: IllustrationFileProps) => (
|
|
235
|
+
<div
|
|
236
|
+
className={cn(
|
|
237
|
+
"relative flex h-[30px] w-6 shrink-0 flex-col gap-[3px] overflow-hidden rounded-[3px] border-[0.5px] border-border-primary bg-surface p-1 shadow-hairline",
|
|
238
|
+
className,
|
|
239
|
+
)}
|
|
134
240
|
aria-hidden
|
|
135
241
|
{...props}
|
|
136
242
|
>
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
className="fill-background-secondary"
|
|
140
|
-
stroke="currentColor"
|
|
141
|
-
strokeWidth="2"
|
|
142
|
-
/>
|
|
143
|
-
<path d="M42 58h125" stroke="currentColor" strokeWidth="2" />
|
|
144
|
-
<path
|
|
145
|
-
d="M59 76h48M59 89h34M59 102h53"
|
|
146
|
-
stroke="currentColor"
|
|
147
|
-
strokeWidth="4"
|
|
148
|
-
strokeLinecap="round"
|
|
149
|
-
opacity=".5"
|
|
150
|
-
/>
|
|
151
|
-
<circle
|
|
152
|
-
cx="155"
|
|
153
|
-
cy="91"
|
|
154
|
-
r="27"
|
|
155
|
-
className="fill-surface text-brand-primary"
|
|
156
|
-
stroke="currentColor"
|
|
157
|
-
strokeWidth="5"
|
|
158
|
-
/>
|
|
159
|
-
<path
|
|
160
|
-
d="m174 111 22 22"
|
|
161
|
-
className="text-brand-primary"
|
|
162
|
-
stroke="currentColor"
|
|
163
|
-
strokeWidth="7"
|
|
164
|
-
strokeLinecap="round"
|
|
165
|
-
/>
|
|
166
|
-
</svg>
|
|
243
|
+
{marks[type]}
|
|
244
|
+
</div>
|
|
167
245
|
)
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
export { Button, buttonVariants, type ButtonProps } from "./button";
|
|
2
2
|
export { Input, inputVariants, type InputProps } from "./input";
|
|
3
|
+
export {
|
|
4
|
+
Kbd,
|
|
5
|
+
KbdGroup,
|
|
6
|
+
kbdVariants,
|
|
7
|
+
type KbdGroupProps,
|
|
8
|
+
type KbdProps,
|
|
9
|
+
} from "./kbd";
|
|
3
10
|
export { Badge, badgeVariants, type BadgeProps } from "./badge";
|
|
4
11
|
export { Switch, type SwitchProps } from "./switch";
|
|
5
12
|
export { Checkbox, type CheckboxProps } from "./checkbox";
|
|
@@ -92,17 +99,27 @@ export {
|
|
|
92
99
|
type AvatarFallbackProps,
|
|
93
100
|
} from "./avatar";
|
|
94
101
|
export { Calendar, type CalendarProps } from "./calendar";
|
|
102
|
+
export {
|
|
103
|
+
DatePicker,
|
|
104
|
+
type DatePickerProps,
|
|
105
|
+
type DatePickerSingleProps,
|
|
106
|
+
type DatePickerRangeProps,
|
|
107
|
+
} from "./date-picker";
|
|
108
|
+
export type { DateRange } from "react-day-picker";
|
|
95
109
|
export {
|
|
96
110
|
Card,
|
|
97
111
|
CardHeader,
|
|
98
112
|
CardTitle,
|
|
99
113
|
CardDescription,
|
|
114
|
+
CardAction,
|
|
100
115
|
CardContent,
|
|
101
116
|
CardFooter,
|
|
117
|
+
cardVariants,
|
|
102
118
|
type CardProps,
|
|
103
119
|
type CardHeaderProps,
|
|
104
120
|
type CardTitleProps,
|
|
105
121
|
type CardDescriptionProps,
|
|
122
|
+
type CardActionProps,
|
|
106
123
|
type CardContentProps,
|
|
107
124
|
type CardFooterProps,
|
|
108
125
|
} from "./card";
|
|
@@ -650,6 +667,7 @@ export {
|
|
|
650
667
|
BreadcrumbLink,
|
|
651
668
|
BreadcrumbPage,
|
|
652
669
|
BreadcrumbSeparator,
|
|
670
|
+
breadcrumbSeparatorVariants,
|
|
653
671
|
type BreadcrumbProps,
|
|
654
672
|
type BreadcrumbListProps,
|
|
655
673
|
type BreadcrumbItemProps,
|
|
@@ -785,13 +803,19 @@ export {
|
|
|
785
803
|
withCentralIconDefaults,
|
|
786
804
|
iconGallery,
|
|
787
805
|
IconBell,
|
|
806
|
+
IconCalendar1,
|
|
788
807
|
IconChainLink1,
|
|
789
808
|
IconCheckmark1,
|
|
790
809
|
IconChevronBottom,
|
|
791
810
|
IconChevronDownSmall,
|
|
792
811
|
IconChevronRightSmall,
|
|
812
|
+
IconCircleCheck,
|
|
813
|
+
IconCircleInfo,
|
|
793
814
|
IconClipboard,
|
|
794
815
|
IconCrossSmall,
|
|
816
|
+
IconDotGrid1x3Horizontal,
|
|
817
|
+
IconExclamationCircle,
|
|
818
|
+
IconExclamationTriangle,
|
|
795
819
|
IconHome,
|
|
796
820
|
IconMagnifyingGlass,
|
|
797
821
|
IconMinus,
|
|
@@ -800,6 +824,7 @@ export {
|
|
|
800
824
|
IconPlus,
|
|
801
825
|
IconSettingsGear1,
|
|
802
826
|
IconSquareBehindSquare6,
|
|
827
|
+
IconStar,
|
|
803
828
|
IconSun,
|
|
804
829
|
IconX,
|
|
805
830
|
type CentralIconProps,
|
|
@@ -821,7 +846,11 @@ export {
|
|
|
821
846
|
IllustrationError,
|
|
822
847
|
IllustrationSuccess,
|
|
823
848
|
IllustrationSearch,
|
|
849
|
+
IllustrationUpload,
|
|
850
|
+
IllustrationFile,
|
|
824
851
|
type IllustrationProps,
|
|
852
|
+
type IllustrationFileProps,
|
|
853
|
+
type FileIllustrationType,
|
|
825
854
|
} from "./illustrations"
|
|
826
855
|
export { BrandMark, BrandWordmark, type BrandMarkProps, type BrandWordmarkProps } from "./brand"
|
|
827
856
|
export {
|
package/src/input.tsx
CHANGED
|
@@ -5,7 +5,7 @@ import { forwardRef, type InputHTMLAttributes } from "react"
|
|
|
5
5
|
import { cn } from "./lib/cn"
|
|
6
6
|
|
|
7
7
|
const inputVariants = cva(
|
|
8
|
-
"text-sm flex w-full cursor-text rounded-md text-fg-primary transition-[color,box-shadow] duration-
|
|
8
|
+
"text-sm flex w-full cursor-text rounded-md text-fg-primary transition-[color,box-shadow] duration-[var(--duration-sm)] ease-enter placeholder:text-fg-quaternary outline-none aria-invalid:border-destructive disabled:cursor-not-allowed disabled:opacity-50",
|
|
9
9
|
{
|
|
10
10
|
variants: {
|
|
11
11
|
variant: {
|
package/src/kbd.tsx
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
2
|
+
import type { ComponentProps } from "react"
|
|
3
|
+
import { cn } from "./lib/cn"
|
|
4
|
+
|
|
5
|
+
const kbdVariants = cva(
|
|
6
|
+
// The border is bottom-only on purpose: it reads as the lip of a physical
|
|
7
|
+
// key without boxing the glyph in on all four sides, which at 12px turns a
|
|
8
|
+
// keycap into a cramped badge.
|
|
9
|
+
"inline-flex shrink-0 items-center justify-center border-b px-1 font-sans text-xs whitespace-nowrap select-none",
|
|
10
|
+
{
|
|
11
|
+
variants: {
|
|
12
|
+
variant: {
|
|
13
|
+
default: "border-border-primary bg-background-tertiary text-fg-secondary",
|
|
14
|
+
// The default fill is a light grey that vanishes on a dark surface,
|
|
15
|
+
// so an inverted keycap lifts off the surface rather than tinting it.
|
|
16
|
+
inverted: "border-white/10 bg-white/10 text-fg-inverted",
|
|
17
|
+
},
|
|
18
|
+
size: {
|
|
19
|
+
sm: "h-4 min-w-3.5 rounded-2xs",
|
|
20
|
+
md: "h-5 min-w-4 rounded-xs",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
defaultVariants: { variant: "default", size: "md" },
|
|
24
|
+
},
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
export type KbdProps = ComponentProps<"kbd"> & VariantProps<typeof kbdVariants>
|
|
28
|
+
export type KbdGroupProps = ComponentProps<"span">
|
|
29
|
+
|
|
30
|
+
/** A single keycap. Presentational — map "cmd" to ⌘ before it gets here. */
|
|
31
|
+
export const Kbd = ({ className, variant, size, ...props }: KbdProps) => (
|
|
32
|
+
<kbd className={cn(kbdVariants({ variant, size }), className)} {...props} />
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
/** A full shortcut. Keeps the caps on one line at a consistent gap. */
|
|
36
|
+
export const KbdGroup = ({ className, ...props }: KbdGroupProps) => (
|
|
37
|
+
<span
|
|
38
|
+
className={cn("inline-flex items-center gap-1 align-middle", className)}
|
|
39
|
+
{...props}
|
|
40
|
+
/>
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
export { kbdVariants }
|
package/src/lib/cn.ts
CHANGED
|
@@ -1,3 +1,45 @@
|
|
|
1
1
|
import { clsx, type ClassValue } from "clsx"
|
|
2
|
+
import { extendTailwindMerge } from "tailwind-merge"
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
/**
|
|
5
|
+
* StandardUI defines typography composites as custom `@utility` rules
|
|
6
|
+
* (`text-sm-strong`, `text-md`, …). Tailwind Merge cannot know those are font
|
|
7
|
+
* sizes, so it files them under text *color* — and then drops them whenever a
|
|
8
|
+
* real color follows, as in `cn("text-sm-strong", "text-fg-primary")`. Grouping
|
|
9
|
+
* them with font sizes keeps them conflicting with each other and with
|
|
10
|
+
* `text-sm`, while leaving colors alone.
|
|
11
|
+
*
|
|
12
|
+
* `shadow-hairline` has the same problem in the shadow group. Custom font sizes
|
|
13
|
+
* (`text-2xs`) and radii (`rounded-2xs`, `rounded-4xl`) already resolve
|
|
14
|
+
* correctly and need no help.
|
|
15
|
+
*
|
|
16
|
+
* Keep this in step with the `@utility` rules in
|
|
17
|
+
* @boyernick/standard-ui-tokens/css/tokens.css.
|
|
18
|
+
*/
|
|
19
|
+
const twMerge = extendTailwindMerge({
|
|
20
|
+
extend: {
|
|
21
|
+
classGroups: {
|
|
22
|
+
"font-size": [
|
|
23
|
+
{
|
|
24
|
+
text: [
|
|
25
|
+
"2xs-strong",
|
|
26
|
+
"xs-strong",
|
|
27
|
+
"sm-strong",
|
|
28
|
+
"md",
|
|
29
|
+
"md-strong",
|
|
30
|
+
"lg-strong",
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
shadow: [{ shadow: ["hairline"] }],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Combines class names and resolves Tailwind conflicts so the last value wins.
|
|
41
|
+
* Without the merge step a caller's `className` cannot override a built-in
|
|
42
|
+
* utility: both classes land in the DOM and the winner is whichever rule sits
|
|
43
|
+
* later in the stylesheet, which the caller does not control.
|
|
44
|
+
*/
|
|
45
|
+
export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs))
|