@boyernick/standard-ui-react 0.2.2-canary.37 → 0.2.2-canary.39

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boyernick/standard-ui-react",
3
- "version": "0.2.2-canary.37",
3
+ "version": "0.2.2-canary.39",
4
4
  "description": "React components for StandardUI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -102,7 +102,11 @@ export const AttachmentPreview = ({
102
102
  }: AttachmentPreviewProps) => (
103
103
  <div
104
104
  className={cn(
105
- "flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-md bg-background-tertiary text-fg-secondary [&_img]:size-full [&_img]:object-cover [&_svg]:size-5",
105
+ // `>svg` rather than a descendant match: an icon handed straight to this
106
+ // slot should be sized to 20, but a composed child that draws its own SVG
107
+ // — a file illustration, say — already knows how big it wants to be, and
108
+ // resizing its inner element letterboxes the drawing off-centre.
109
+ "flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-md bg-background-tertiary text-fg-secondary [&_img]:size-full [&_img]:object-cover [&>svg]:size-5",
106
110
  className,
107
111
  )}
108
112
  {...props}
@@ -263,7 +263,7 @@ export const AutocompleteSeparator = ({
263
263
  ...props
264
264
  }: AutocompleteSeparatorProps) => (
265
265
  <BaseAutocomplete.Separator
266
- className={cn("my-1 h-px bg-border-primary", className)}
266
+ className={cn("mx-3 my-1 h-px bg-border-primary", className)}
267
267
  {...props}
268
268
  />
269
269
  )
package/src/combobox.tsx CHANGED
@@ -271,7 +271,7 @@ export const ComboboxSeparator = ({
271
271
  ...props
272
272
  }: ComboboxSeparatorProps) => (
273
273
  <BaseCombobox.Separator
274
- className={cn("my-1 h-px bg-border-primary", className)}
274
+ className={cn("mx-3 my-1 h-px bg-border-primary", className)}
275
275
  {...props}
276
276
  />
277
277
  )
@@ -293,7 +293,12 @@ export const ComboboxChips = ({ className, ...props }: ComboboxChipsProps) => (
293
293
  export const ComboboxChip = ({ className, ...props }: ComboboxChipProps) => (
294
294
  <BaseCombobox.Chip
295
295
  className={cn(
296
- "inline-flex h-6 items-center gap-1 rounded-xs bg-background-tertiary px-1.5 text-xs-strong text-fg-primary",
296
+ // Metrics are optical, not nominal. At 24px tall with 12px text the cap
297
+ // block leaves 7.9px above it, so `px-2` matches the sides to that rather
298
+ // than to a smaller nominal number; `gap-0.5` lands 6px of *visible* space
299
+ // before the cross, which carries 4.08px of its own clearance; and
300
+ // `rounded-sm` is what Badge already uses for a label this size.
301
+ "inline-flex h-6 items-center gap-0.5 rounded-sm bg-background-tertiary px-2 text-xs-strong text-fg-primary",
297
302
  className,
298
303
  )}
299
304
  {...props}
@@ -307,7 +312,13 @@ export const ComboboxChipRemove = ({
307
312
  }: ComboboxChipRemoveProps) => (
308
313
  <BaseCombobox.ChipRemove
309
314
  className={cn(
310
- "inline-flex size-3.5 cursor-pointer items-center justify-center text-fg-primary outline-none focus-visible:outline-none",
315
+ // `-mr-1` is optical compensation, not spacing: the cross glyph is drawn
316
+ // across the middle 10 units of a 24 viewBox, so a 14px icon box carries
317
+ // 4.08px of its own clearance on each side. Without this the chip's right
318
+ // edge reads ~10px away from the mark against 6px of padding on the left.
319
+ // It lives on the remove button rather than in the chip's padding so a
320
+ // chip with no remove button stays symmetric.
321
+ "-mr-1 inline-flex size-3.5 cursor-pointer items-center justify-center text-fg-primary outline-none focus-visible:outline-none",
311
322
  className,
312
323
  )}
313
324
  {...props}
@@ -151,7 +151,7 @@ export const ContextMenuSeparator = ({
151
151
  ...props
152
152
  }: ContextMenuSeparatorProps) => (
153
153
  <BaseContextMenu.Separator
154
- className={cn("my-1 h-px bg-border-primary", className)}
154
+ className={cn("mx-3 my-1 h-px bg-border-primary", className)}
155
155
  {...props}
156
156
  />
157
157
  )
@@ -2,13 +2,28 @@ 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.
5
+ * Small illustrations for empty states.
6
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
7
+ * Drawn as SVG on one grid so the whole family shares a stroke weight, a corner
8
+ * radius and an optical bounding box — a row of them lines up without ad-hoc
9
+ * sizing. Colour still comes from theme tokens (`fill-*`/`stroke-*` utilities
10
+ * resolve the same variables the rest of the library uses), so they follow the
11
+ * palette into dark mode with nothing baked in.
12
+ *
13
+ * Deliberately abstract and monochrome — a miniature of the thing that is
10
14
  * missing, not a picture about it. Nothing here uses a brand colour: an empty
11
15
  * state is a quiet moment, and an accent turns it into an announcement.
16
+ *
17
+ * Three levels of ink, and no others:
18
+ * structure `border-secondary` the edge of a sheet or a folder
19
+ * content `fg-quaternary/60` marks standing in for what is on it
20
+ * subject `fg-tertiary/70` the one thing the illustration is about
21
+ *
22
+ * The opacity modifiers are the whole point of those last two: the foreground
23
+ * tokens at full strength are text ink, and text ink inside a 32px sheet reads
24
+ * as a document someone is expected to be able to *read*. Stepped back, the
25
+ * marks stay legible but sit behind the silhouette, which is what carries the
26
+ * meaning. They are still tokens, so both levels flip with the theme.
12
27
  */
13
28
 
14
29
  export type IllustrationProps = ComponentProps<"div">
@@ -27,89 +42,241 @@ const Frame = ({ className, children, ...props }: IllustrationProps) => (
27
42
  </div>
28
43
  )
29
44
 
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
- )}
45
+ /**
46
+ * The grid every illustration is drawn on: 64×48 user units rendered at 64×48
47
+ * CSS pixels, so one unit is one pixel. Geometry sits on half-unit coordinates
48
+ * for that reason — a 1-wide stroke centred on x.5 covers exactly one pixel
49
+ * instead of straddling two and rendering soft.
50
+ */
51
+ const Stage = ({ children }: { children: ReactNode }) => (
52
+ <svg
53
+ viewBox="0 0 64 48"
54
+ fill="none"
55
+ strokeWidth={1}
56
+ strokeLinecap="round"
57
+ strokeLinejoin="round"
58
+ className={cn("size-full", SHADOW)}
44
59
  >
45
60
  {children}
46
- </div>
61
+ </svg>
47
62
  )
48
63
 
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
- />
64
+ /**
65
+ * Lift off the page. `drop-shadow` rather than `shadow-*`: a box-shadow needs a
66
+ * box, and these are paths — it would draw a rectangle behind a transparent
67
+ * stage. This follows the silhouette instead, so the fold and the torn edge
68
+ * cast a shadow of their own shape.
69
+ *
70
+ * Built from the same variables as the shadow scale, so the alpha steps up in
71
+ * dark mode the way every other shadow in the library does. `overflow-visible`
72
+ * on the stage keeps the blur from being clipped at the viewBox edge, which
73
+ * the 24×30 sheet would otherwise do — it sits half a unit from the bottom.
74
+ */
75
+ const SHADOW =
76
+ "overflow-visible [filter:drop-shadow(0_1px_2px_rgb(var(--shadow-color)/var(--shadow-alpha-200)))]"
77
+
78
+ const SHEET = "fill-surface stroke-border-secondary"
79
+ const MARK = "fill-fg-quaternary/60"
80
+ const SUBJECT = "stroke-fg-tertiary/70"
81
+
82
+ /** Filled *and* stroked in the same ink. The stage sets `stroke-linejoin:
83
+ * round`, so the outline rounds the points of a triangle — a plain fill
84
+ * leaves them needle-sharp, which is the one thing in here that never looks
85
+ * drawn. The path is inset by half the stroke to keep the size honest. */
86
+ const MARK_SOLID = "fill-fg-quaternary/60 stroke-fg-quaternary/60"
87
+
88
+ /** The sheet, and the line that closes its folded corner.
89
+ *
90
+ * The fold is what makes a rounded rectangle read as paper. Without it every
91
+ * illustration in the family opens on the same anonymous box. */
92
+ const SHEET_PATH =
93
+ "M22.5 8.5h12l9 9v22a2 2 0 0 1-2 2h-19a2 2 0 0 1-2-2v-29a2 2 0 0 1 2-2z"
94
+ const FOLD_PATH = "M34.5 8.5v7a2 2 0 0 0 2 2h7"
95
+
96
+ const Sheet = () => (
97
+ <>
98
+ <path d={SHEET_PATH} className={SHEET} />
99
+ <path d={FOLD_PATH} className="stroke-border-secondary" />
100
+ </>
101
+ )
102
+
103
+ /** A line of content on a sheet, on its inner measure of x 25 → 39. */
104
+ const Mark = ({ x = 25, y, w }: { x?: number; y: number; w: number }) => (
105
+ <rect x={x} y={y} width={w} height={1.5} rx={0.75} className={MARK} />
55
106
  )
56
107
 
57
108
  /** Nothing here yet — a sheet with no content on it. */
58
109
  export const IllustrationEmpty = ({ className, ...props }: IllustrationProps) => (
59
110
  <Frame className={className} {...props}>
60
- <Card className="h-10 w-8" />
111
+ <Stage>
112
+ <Sheet />
113
+ </Stage>
61
114
  </Frame>
62
115
  )
63
116
 
64
- /** Something to look through — a sheet behind a lens. */
117
+ /** Something to look through — a lens over the corner of a sheet.
118
+ *
119
+ * The lens breaks the corner rather than the side. Sitting against the side it
120
+ * reads as the handle of a mug, which is what the previous drawing did. */
65
121
  export const IllustrationSearch = ({
66
122
  className,
67
123
  ...props
68
124
  }: IllustrationProps) => (
69
125
  <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" />
126
+ <Stage>
127
+ <Sheet />
128
+ <Mark y={25} w={10} />
129
+ <Mark y={28.5} w={7.5} />
130
+ <Mark y={32} w={5} />
131
+ <circle
132
+ cx={42.5}
133
+ cy={34.5}
134
+ r={5.5}
135
+ className={cn("fill-surface", SUBJECT)}
136
+ strokeWidth={1.25}
137
+ />
138
+ <path
139
+ d="M46.5 38.5 49.5 41.5"
140
+ className={SUBJECT}
141
+ strokeWidth={1.25}
142
+ />
143
+ </Stage>
144
+ </Frame>
145
+ )
146
+
147
+ /** Searched, and there was nothing to find — the same lens over a blank sheet.
148
+ *
149
+ * The pair is the point: `IllustrationSearch` has content under the lens and
150
+ * means "there is something here to look through"; this one has none, and
151
+ * means the looking is already done. */
152
+ export const IllustrationNoResults = ({
153
+ className,
154
+ ...props
155
+ }: IllustrationProps) => (
156
+ <Frame className={className} {...props}>
157
+ <Stage>
158
+ <Sheet />
159
+ <circle
160
+ cx={42.5}
161
+ cy={34.5}
162
+ r={5.5}
163
+ className={cn("fill-surface", SUBJECT)}
164
+ strokeWidth={1.25}
165
+ />
166
+ <path d="M46.5 38.5 49.5 41.5" className={SUBJECT} strokeWidth={1.25} />
167
+ </Stage>
168
+ </Frame>
169
+ )
170
+
171
+ /** Not yours to open — a sheet under a closed clasp.
172
+ *
173
+ * The sheet is blank on purpose: the reader cannot see what is on it, so
174
+ * drawing content would promise something the state does not deliver. */
175
+ export const IllustrationLocked = ({
176
+ className,
177
+ ...props
178
+ }: IllustrationProps) => (
179
+ <Frame className={className} {...props}>
180
+ <Stage>
181
+ <Sheet />
182
+ <path
183
+ d="M29.5 25.5v-2.5a2.5 2.5 0 0 1 5 0v2.5"
184
+ className={SUBJECT}
185
+ strokeWidth={1.25}
186
+ />
187
+ <rect
188
+ x={27}
189
+ y={25.5}
190
+ width={10}
191
+ height={8}
192
+ rx={1.5}
193
+ className={cn("fill-surface", SUBJECT)}
194
+ strokeWidth={1.25}
195
+ />
196
+ <circle cx={32} cy={29.5} r={1} className={MARK} />
197
+ </Stage>
76
198
  </Frame>
77
199
  )
78
200
 
79
- /** Something went wrongthe stack knocked out of true. */
201
+ /** Nothing plotted yeta sheet carrying an axis and no series.
202
+ *
203
+ * Stubs on the baseline rather than a bare axis: an empty pair of lines reads
204
+ * as a drawing that failed to finish, while columns flattened to nothing read
205
+ * as a chart that ran and found no values. */
206
+ export const IllustrationNoData = ({
207
+ className,
208
+ ...props
209
+ }: IllustrationProps) => (
210
+ <Frame className={className} {...props}>
211
+ <Stage>
212
+ <Sheet />
213
+ <path d="M25.5 19.5v14h13" className="stroke-fg-quaternary/60" />
214
+ <rect x={27.5} y={31.5} width={3} height={2} rx={0.75} className={MARK} />
215
+ <rect x={31.5} y={31.5} width={3} height={2} rx={0.75} className={MARK} />
216
+ <rect x={35.5} y={31.5} width={3} height={2} rx={0.75} className={MARK} />
217
+ </Stage>
218
+ </Frame>
219
+ )
220
+
221
+ /** Something went wrong — the sheet torn across, the lower half slipped.
222
+ *
223
+ * Two tilted sheets read as "some documents"; a tear reads as damage without
224
+ * stamping a symbol on anything. */
80
225
  export const IllustrationError = ({ className, ...props }: IllustrationProps) => (
81
226
  <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>
227
+ <Stage>
228
+ <path
229
+ d="M20.5 10.5a2 2 0 0 1 2-2h12l9 9v11.5L39.5 31 35.5 28.5 31.5 31.5 27.5 28.5 23.5 31 20.5 28.5z"
230
+ className={SHEET}
231
+ />
232
+ <path d={FOLD_PATH} className="stroke-border-secondary" />
233
+ <Mark y={21} w={10} />
234
+ <Mark y={24.5} w={6.5} />
235
+ <path
236
+ d="M23 30 26 32.5 30 29.5 34 32.5 38 30 42 32.5 46 30v9.5a2 2 0 0 1-2 2H25a2 2 0 0 1-2-2z"
237
+ className={SHEET}
238
+ />
239
+ </Stage>
87
240
  </Frame>
88
241
  )
89
242
 
90
243
  /** Somewhere to put a file — a sheet going into a folder.
91
244
  *
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. */
245
+ * The folder is the one shape here that is not another sheet, which keeps it
246
+ * from reading as the torn stack. Its tab and body are a single outline, so
247
+ * they meet as one silhouette instead of two rectangles that nearly touch. */
98
248
  export const IllustrationUpload = ({
99
249
  className,
100
250
  ...props
101
251
  }: IllustrationProps) => (
102
252
  <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>
253
+ <Stage>
254
+ {/* One closed silhouette carries every outer edge of the folder — both
255
+ sides, the tab, the bottom. Drawing the back and the front as two
256
+ overlapping outlines painted `border-secondary` over itself wherever
257
+ they shared an edge, and the token is an alpha, so a doubled edge
258
+ composites to roughly twice the strength and reads as a darker line. */}
259
+ <path
260
+ d="M18.5 39.5V22.5a2 2 0 0 1 2-2H26l2.5 3h15a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-23a2 2 0 0 1-2-2z"
261
+ className={SHEET}
262
+ />
263
+ {/* Open at the bottom: the front covers it, and a closed path would put a
264
+ second stroke across the sheet where the two meet. */}
265
+ <path d="M27.5 27V7.5a2 2 0 0 1 2-2h8l4 4V27" className={SHEET} />
266
+ <path
267
+ d="M37.5 5.5v2a2 2 0 0 0 2 2h2"
268
+ className="stroke-border-secondary"
269
+ />
270
+ <Mark x={31} y={14.5} w={6} />
271
+ <Mark x={31} y={18} w={4} />
272
+ {/* The front of the folder: a fill inset to sit *inside* the silhouette's
273
+ stroke, plus the one edge that is genuinely its own. */}
274
+ <path
275
+ d="M19 27h26v12.5a1.5 1.5 0 0 1-1.5 1.5h-23A1.5 1.5 0 0 1 19 39.5z"
276
+ className="fill-background-tertiary"
277
+ />
278
+ <path d="M18.5 26.5h27" className="stroke-border-secondary" />
279
+ </Stage>
113
280
  </Frame>
114
281
  )
115
282
 
@@ -119,20 +286,24 @@ export const IllustrationSuccess = ({
119
286
  ...props
120
287
  }: IllustrationProps) => (
121
288
  <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>
289
+ <Stage>
290
+ <Sheet />
291
+ <path
292
+ d="M28 29 31.5 32.5 37.5 25.5"
293
+ className={SUBJECT}
294
+ strokeWidth={1.75}
295
+ />
296
+ </Stage>
126
297
  </Frame>
127
298
  )
128
299
 
129
300
  /* -------------------------------------------------------------------------
130
301
  * File types
131
302
  *
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.
303
+ * A second family on the same grammar, drawn on its own 24×30 grid: these sit
304
+ * inline — in an attachment row, a list, a preview slot — so they are a bare
305
+ * sheet rather than a hero on a stage. Same sheet, same fold, same three
306
+ * levels of ink; only the mark on the sheet changes.
136
307
  *
137
308
  * Presentational, like Kbd: they render the type you name. Mapping an
138
309
  * extension to a type is the consumer's job, since only the consumer knows
@@ -154,74 +325,138 @@ export type IllustrationFileProps = IllustrationProps & {
154
325
  type?: FileIllustrationType
155
326
  }
156
327
 
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)} />
328
+ /** 24×30 user units at 24×30 CSS pixels, so a unit is a pixel here too. */
329
+ const FileStage = ({ children }: { children: ReactNode }) => (
330
+ <svg
331
+ viewBox="0 0 24 30"
332
+ fill="none"
333
+ strokeWidth={1}
334
+ strokeLinecap="round"
335
+ strokeLinejoin="round"
336
+ className={cn("size-full", SHADOW)}
337
+ >
338
+ {children}
339
+ </svg>
160
340
  )
161
341
 
342
+ const FILE_SHEET_PATH =
343
+ "M3.5 .5h12l7 7v20a2 2 0 0 1-2 2h-17a2 2 0 0 1-2-2V2.5a2 2 0 0 1 2-2z"
344
+ const FILE_FOLD_PATH = "M15.5 .5v5a2 2 0 0 0 2 2h5"
345
+
346
+ /** A line of content, on the sheet's inner measure of x 5 → 19. */
347
+ const FileMark = ({
348
+ x = 6.5,
349
+ y,
350
+ w,
351
+ h = 1.5,
352
+ }: {
353
+ x?: number
354
+ y: number
355
+ w: number
356
+ h?: number
357
+ }) => <rect x={x} y={y} width={w} height={h} rx={h / 2} className={MARK} />
358
+
359
+ /** Marks are centred in the space below the fold, y 9 → 27. */
162
360
  const marks: Record<FileIllustrationType, ReactNode> = {
163
361
  document: (
164
362
  <>
165
- <Bar width="100%" />
166
- <Bar width="100%" />
167
- <Bar width="60%" />
363
+ <FileMark y={14} w={10} />
364
+ <FileMark y={17.5} w={10} />
365
+ <FileMark y={21} w={6.5} />
168
366
  </>
169
367
  ),
170
368
  // A horizon and a sun — the smallest arrangement that reads as a picture.
171
369
  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>
370
+ <>
371
+ <circle cx={15.5} cy={14} r={1.2} className={MARK} />
372
+ <path
373
+ d="M7 20.5l3-3.75 2 2.75 1.75-2L17 20.5z"
374
+ className={MARK_SOLID}
375
+ />
376
+ </>
177
377
  ),
178
378
  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>
379
+ <>
380
+ {[14, 17.5, 21].map((y) =>
381
+ [6.5, 10.5, 14.5].map((x) => (
382
+ <rect
383
+ key={`${x}-${y}`}
384
+ x={x}
385
+ y={y}
386
+ width={3}
387
+ height={2.5}
388
+ rx={0.5}
389
+ className={MARK}
390
+ />
391
+ )),
392
+ )}
393
+ </>
184
394
  ),
185
395
  presentation: (
186
396
  <>
187
- <Block className="h-2.5 w-full" />
188
- <Bar width="70%" />
397
+ <rect x={7} y={13.5} width={10} height={6} rx={1} className={MARK} />
398
+ <FileMark x={7} y={21.5} w={6} />
189
399
  </>
190
400
  ),
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
- ),
401
+ video: <path d="M10 14.75 15.25 18 10 21.25z" className={MARK_SOLID} />,
197
402
  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>
403
+ <>
404
+ {[
405
+ [6.875, 16.5, 3],
406
+ [9.125, 15.25, 5.5],
407
+ [11.375, 14, 8],
408
+ [13.625, 15.25, 5.5],
409
+ [15.875, 16.5, 3],
410
+ ].map(([x, y, h]) => (
411
+ <rect
412
+ key={x}
413
+ x={x}
414
+ y={y}
415
+ width={1.25}
416
+ height={h}
417
+ rx={0.625}
418
+ className={MARK}
419
+ />
420
+ ))}
421
+ </>
205
422
  ),
423
+ // Chevrons rather than indented lines: at 24px the indents read as ragged
424
+ // text, while the brackets say "code" on their own.
206
425
  code: (
207
426
  <>
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%" />
427
+ <path
428
+ d="M10 14.5 7.5 18 10 21.5"
429
+ className="stroke-fg-quaternary/60"
430
+ strokeWidth={1.25}
431
+ />
432
+ <path
433
+ d="M14 14.5 16.5 18 14 21.5"
434
+ className="stroke-fg-quaternary/60"
435
+ strokeWidth={1.25}
436
+ />
216
437
  </>
217
438
  ),
218
- // A stack, since an archive is other files in a coat.
439
+ // Sheets inside a sheet, since an archive is other files in a coat. Bars in
440
+ // any arrangement just read as a heavier `document`.
219
441
  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>
442
+ <>
443
+ <rect
444
+ x={6.5}
445
+ y={13.5}
446
+ width={6}
447
+ height={7.5}
448
+ rx={1}
449
+ className="fill-surface stroke-fg-quaternary/60"
450
+ />
451
+ <rect
452
+ x={10.5}
453
+ y={16}
454
+ width={6}
455
+ height={7.5}
456
+ rx={1}
457
+ className="fill-surface stroke-fg-quaternary/60"
458
+ />
459
+ </>
225
460
  ),
226
461
  generic: null,
227
462
  }
@@ -233,13 +468,14 @@ export const IllustrationFile = ({
233
468
  ...props
234
469
  }: IllustrationFileProps) => (
235
470
  <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
- )}
471
+ className={cn("inline-flex h-[30px] w-6 shrink-0", className)}
240
472
  aria-hidden
241
473
  {...props}
242
474
  >
243
- {marks[type]}
475
+ <FileStage>
476
+ <path d={FILE_SHEET_PATH} className={SHEET} />
477
+ <path d={FILE_FOLD_PATH} className="stroke-border-secondary" />
478
+ {marks[type]}
479
+ </FileStage>
244
480
  </div>
245
481
  )
package/src/index.ts CHANGED
@@ -1016,6 +1016,9 @@ export {
1016
1016
  export {
1017
1017
  IllustrationEmpty,
1018
1018
  IllustrationError,
1019
+ IllustrationLocked,
1020
+ IllustrationNoData,
1021
+ IllustrationNoResults,
1019
1022
  IllustrationSuccess,
1020
1023
  IllustrationSearch,
1021
1024
  IllustrationUpload,
package/src/menu.tsx CHANGED
@@ -112,7 +112,7 @@ export const MenuLinkItem = ({ className, ...props }: MenuLinkItemProps) => (
112
112
 
113
113
  export const MenuSeparator = ({ className, ...props }: MenuSeparatorProps) => (
114
114
  <BaseMenu.Separator
115
- className={cn("my-1 h-px bg-border-primary", className)}
115
+ className={cn("mx-3 my-1 h-px bg-border-primary", className)}
116
116
  {...props}
117
117
  />
118
118
  )
package/src/select.tsx CHANGED
@@ -160,7 +160,7 @@ export const SelectSeparator = ({
160
160
  ...props
161
161
  }: SelectSeparatorProps) => (
162
162
  <BaseSelect.Separator
163
- className={cn("my-1 h-px bg-border-primary", className)}
163
+ className={cn("mx-3 my-1 h-px bg-border-primary", className)}
164
164
  {...props}
165
165
  />
166
166
  )