@boyernick/standard-ui-react 0.1.1-canary.9 → 0.2.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/package.json +5 -3
- package/src/accordion.tsx +5 -2
- package/src/alert-dialog.tsx +1 -1
- package/src/attachment.tsx +5 -2
- package/src/autocomplete.tsx +4 -3
- package/src/block-editor.tsx +1747 -0
- package/src/brand.tsx +2 -2
- package/src/breadcrumb.tsx +14 -7
- package/src/button.tsx +22 -11
- package/src/calendar.tsx +6 -3
- package/src/carousel.tsx +220 -53
- package/src/checkbox.tsx +3 -2
- package/src/code-block.tsx +66 -5
- package/src/collapsible.tsx +4 -2
- package/src/combobox.tsx +4 -3
- package/src/command.tsx +27 -12
- package/src/dialog.tsx +33 -10
- package/src/empty.tsx +4 -4
- package/src/field.tsx +4 -3
- package/src/filter-group.tsx +143 -0
- package/src/icons.tsx +28 -1
- package/src/illustrations.tsx +219 -141
- package/src/index.ts +238 -18
- package/src/input.tsx +8 -5
- package/src/kbd.tsx +10 -4
- package/src/lib/focus.ts +41 -0
- package/src/lib/popup.ts +1 -1
- package/src/lifeline/company-icon.tsx +71 -0
- package/src/lifeline/index.ts +42 -0
- package/src/lifeline/lifeline-data.ts +97 -0
- package/src/lifeline/lifeline-desktop.tsx +204 -0
- package/src/lifeline/lifeline-event.tsx +108 -0
- package/src/lifeline/lifeline-fireworks.tsx +302 -0
- package/src/lifeline/lifeline-hover-image.tsx +269 -0
- package/src/lifeline/lifeline-icons.tsx +39 -0
- package/src/lifeline/lifeline-intro-timing.ts +105 -0
- package/src/lifeline/lifeline-labels.tsx +24 -0
- package/src/lifeline/lifeline-layout.ts +1 -0
- package/src/lifeline/lifeline-legend.tsx +31 -0
- package/src/lifeline/lifeline-lightbox.tsx +309 -0
- package/src/lifeline/lifeline-marker.tsx +183 -0
- package/src/lifeline/lifeline-people.tsx +99 -0
- package/src/lifeline/lifeline-photos.tsx +366 -0
- package/src/lifeline/lifeline-shell.tsx +135 -0
- package/src/lifeline/lifeline-utils.ts +83 -0
- package/src/lifeline/lifeline-vertical.tsx +482 -0
- package/src/lifeline/lifeline.tsx +69 -0
- package/src/lifeline/types.ts +112 -0
- package/src/lifeline/use-lifeline-intro.ts +130 -0
- package/src/lifeline/use-lifeline-scroll.ts +1011 -0
- package/src/lifeline/use-lifeline-vertical-scroll.ts +271 -0
- package/src/menubar.tsx +9 -2
- package/src/minimap.tsx +296 -0
- package/src/modal.tsx +608 -0
- package/src/navigation-menu.tsx +338 -67
- package/src/number-field.tsx +336 -61
- package/src/otp-field.tsx +4 -3
- package/src/pagination.tsx +262 -42
- package/src/password-protection.tsx +345 -0
- package/src/popover.tsx +1 -1
- package/src/progress.tsx +5 -0
- package/src/questionnaire.tsx +284 -0
- package/src/radio.tsx +4 -2
- package/src/scroll-area.tsx +4 -1
- package/src/select.tsx +5 -2
- package/src/sidebar.tsx +113 -28
- package/src/slider.tsx +66 -6
- package/src/sounds.tsx +6 -10
- package/src/spinner.tsx +105 -32
- package/src/switch.tsx +4 -1
- package/src/table.tsx +82 -15
- package/src/tabs.tsx +189 -38
- package/src/text-animate.tsx +71 -28
- package/src/textarea.tsx +6 -1
- package/src/timeline.tsx +378 -0
- package/src/toast.tsx +214 -35
- package/src/toggle.tsx +4 -2
- package/src/toolbar.tsx +12 -7
- package/src/tooltip.tsx +43 -3
- package/src/video-player.tsx +396 -127
- package/src/image-modal.tsx +0 -110
- package/src/markdown-editor.tsx +0 -302
package/src/code-block.tsx
CHANGED
|
@@ -4,6 +4,10 @@ import { useState } from "react"
|
|
|
4
4
|
import { highlight, type LanguageName } from "sugar-high"
|
|
5
5
|
import { Button } from "./button"
|
|
6
6
|
import { IconCheckmark1, IconSquareBehindSquare6 } from "./icons"
|
|
7
|
+
import { cn } from "./lib/cn"
|
|
8
|
+
|
|
9
|
+
/** 1-based line number, or an inclusive `[start, end]` range. */
|
|
10
|
+
export type CodeBlockHighlightLine = number | readonly [number, number]
|
|
7
11
|
|
|
8
12
|
export type CodeBlockProps = {
|
|
9
13
|
code: string
|
|
@@ -13,12 +17,18 @@ export type CodeBlockProps = {
|
|
|
13
17
|
showHeader?: boolean
|
|
14
18
|
/** Removes border and radius chrome for nesting inside another frame. */
|
|
15
19
|
bare?: boolean
|
|
20
|
+
/** 1-based lines (or ranges) to emphasize inside the block. */
|
|
21
|
+
highlightLines?: readonly CodeBlockHighlightLine[]
|
|
22
|
+
/** 1-based lines added in a review (green). */
|
|
23
|
+
addedLines?: readonly CodeBlockHighlightLine[]
|
|
24
|
+
/** 1-based lines removed in a review (red). */
|
|
25
|
+
removedLines?: readonly CodeBlockHighlightLine[]
|
|
16
26
|
className?: string
|
|
17
27
|
}
|
|
18
28
|
|
|
19
29
|
const sizeClass = {
|
|
20
|
-
sm: "text-2xs
|
|
21
|
-
md: "text-xs
|
|
30
|
+
sm: "text-2xs py-3",
|
|
31
|
+
md: "text-xs py-4",
|
|
22
32
|
} as const
|
|
23
33
|
|
|
24
34
|
const languages = new Set<LanguageName>([
|
|
@@ -59,17 +69,60 @@ const getHighlightLanguage = (lang: string): LanguageName => {
|
|
|
59
69
|
return "typescript"
|
|
60
70
|
}
|
|
61
71
|
|
|
72
|
+
const toHighlightedLines = (
|
|
73
|
+
lines?: readonly CodeBlockHighlightLine[],
|
|
74
|
+
): Set<number> => {
|
|
75
|
+
const set = new Set<number>()
|
|
76
|
+
if (!lines) return set
|
|
77
|
+
|
|
78
|
+
for (const entry of lines) {
|
|
79
|
+
if (typeof entry === "number") {
|
|
80
|
+
set.add(entry)
|
|
81
|
+
continue
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const [start, end] = entry
|
|
85
|
+
const from = Math.min(start, end)
|
|
86
|
+
const to = Math.max(start, end)
|
|
87
|
+
for (let line = from; line <= to; line += 1) set.add(line)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return set
|
|
91
|
+
}
|
|
92
|
+
|
|
62
93
|
export const CodeBlock = ({
|
|
63
94
|
code: rawCode,
|
|
64
95
|
lang = "tsx",
|
|
65
96
|
size = "md",
|
|
66
97
|
showHeader = true,
|
|
67
98
|
bare = false,
|
|
99
|
+
highlightLines,
|
|
100
|
+
addedLines,
|
|
101
|
+
removedLines,
|
|
68
102
|
className = "",
|
|
69
103
|
}: CodeBlockProps) => {
|
|
70
104
|
const [copied, setCopied] = useState(false)
|
|
71
105
|
const code = rawCode.replace(/^\n/, "").replace(/\n$/, "")
|
|
72
|
-
const
|
|
106
|
+
const focused = toHighlightedLines(highlightLines)
|
|
107
|
+
const added = toHighlightedLines(addedLines)
|
|
108
|
+
const removed = toHighlightedLines(removedLines)
|
|
109
|
+
const html = highlight(code, {
|
|
110
|
+
lang: getHighlightLanguage(lang),
|
|
111
|
+
markLine: (line) => {
|
|
112
|
+
const number = line.index + 1
|
|
113
|
+
if (added.has(number)) {
|
|
114
|
+
line.className += " sh__line--added"
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
if (removed.has(number)) {
|
|
118
|
+
line.className += " sh__line--removed"
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
if (focused.has(number)) {
|
|
122
|
+
line.className += " sh__line--highlighted"
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
})
|
|
73
126
|
const frameClass =
|
|
74
127
|
!showHeader && !bare
|
|
75
128
|
? "rounded-lg border border-border-primary bg-surface"
|
|
@@ -87,7 +140,11 @@ export const CodeBlock = ({
|
|
|
87
140
|
|
|
88
141
|
const codeContent = (
|
|
89
142
|
<pre
|
|
90
|
-
className={
|
|
143
|
+
className={cn(
|
|
144
|
+
"sh-code overflow-x-auto font-mono",
|
|
145
|
+
sizeClass[size],
|
|
146
|
+
frameClass,
|
|
147
|
+
)}
|
|
91
148
|
dangerouslySetInnerHTML={{ __html: html }}
|
|
92
149
|
/>
|
|
93
150
|
)
|
|
@@ -98,7 +155,11 @@ export const CodeBlock = ({
|
|
|
98
155
|
|
|
99
156
|
return (
|
|
100
157
|
<div
|
|
101
|
-
|
|
158
|
+
data-slot="code-block"
|
|
159
|
+
className={cn(
|
|
160
|
+
"overflow-hidden rounded-xl border border-border-primary bg-surface",
|
|
161
|
+
className,
|
|
162
|
+
)}
|
|
102
163
|
>
|
|
103
164
|
<div className="flex items-center justify-between border-b border-border-primary bg-background-secondary px-3 py-1.5 pl-4">
|
|
104
165
|
<p className="text-xs font-mono text-fg-quaternary">{lang}</p>
|
package/src/collapsible.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import { Collapsible as BaseCollapsible } from "@base-ui/react/collapsible"
|
|
|
4
4
|
import type { ComponentProps } from "react"
|
|
5
5
|
import { IconChevronDownSmall } from "./icons"
|
|
6
6
|
import { cn } from "./lib/cn"
|
|
7
|
+
import { focusRing } from "./lib/focus"
|
|
7
8
|
import { motion } from "./lib/motion"
|
|
8
9
|
|
|
9
10
|
export type CollapsibleProps = ComponentProps<typeof BaseCollapsible.Root>
|
|
@@ -23,9 +24,10 @@ export const CollapsibleTrigger = ({
|
|
|
23
24
|
}: CollapsibleTriggerProps) => (
|
|
24
25
|
<BaseCollapsible.Trigger
|
|
25
26
|
className={cn(
|
|
26
|
-
"group flex h-9 w-full cursor-pointer items-center justify-between gap-2 rounded-md border border-border-secondary bg-surface px-3 text-sm text-fg-primary inset-shadow-outline-top
|
|
27
|
+
"group flex h-9 w-full cursor-pointer items-center justify-between gap-2 rounded-md border border-border-secondary bg-surface px-3 text-sm text-fg-primary inset-shadow-outline-top",
|
|
27
28
|
motion.colors,
|
|
28
|
-
|
|
29
|
+
focusRing,
|
|
30
|
+
"data-disabled:cursor-not-allowed data-disabled:opacity-50",
|
|
29
31
|
className,
|
|
30
32
|
)}
|
|
31
33
|
{...props}
|
package/src/combobox.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import { Combobox as BaseCombobox } from "@base-ui/react/combobox"
|
|
|
4
4
|
import type { ComponentProps, ReactElement } from "react"
|
|
5
5
|
import { IconChevronDownSmall, IconCrossSmall } from "./icons"
|
|
6
6
|
import { cn } from "./lib/cn"
|
|
7
|
+
import { focusRingWithin, focusRingInvalidWithin } from "./lib/focus"
|
|
7
8
|
import { motion } from "./lib/motion"
|
|
8
9
|
import { popupInset, popupItem, popupLabel, popupMessage, popupSurface } from "./lib/popup"
|
|
9
10
|
|
|
@@ -77,9 +78,9 @@ export const ComboboxInputGroup = ({
|
|
|
77
78
|
}: ComboboxInputGroupProps) => (
|
|
78
79
|
<BaseCombobox.InputGroup
|
|
79
80
|
className={cn(
|
|
80
|
-
"relative flex min-h-9 w-full items-center rounded-md border border-border-secondary bg-surface inset-shadow-outline-top
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
"relative flex min-h-9 w-full items-center rounded-md border border-border-secondary bg-surface inset-shadow-outline-top transition-[color,box-shadow]",
|
|
82
|
+
focusRingWithin,
|
|
83
|
+
focusRingInvalidWithin,
|
|
83
84
|
"has-[[aria-invalid=true]]:border-destructive has-[[aria-invalid=true]]:focus-within:border-destructive has-[[aria-invalid=true]]:focus-within:ring-destructive/20",
|
|
84
85
|
"has-[:disabled]:opacity-50",
|
|
85
86
|
className,
|
package/src/command.tsx
CHANGED
|
@@ -15,7 +15,9 @@ import {
|
|
|
15
15
|
type ReactNode,
|
|
16
16
|
} from "react"
|
|
17
17
|
import { IconCrossSmall, IconMagnifyingGlass } from "./icons"
|
|
18
|
+
import { filterGroupVariants, filterItemVariants } from "./filter-group"
|
|
18
19
|
import { cn } from "./lib/cn"
|
|
20
|
+
import { focusRing } from "./lib/focus"
|
|
19
21
|
import { motion } from "./lib/motion"
|
|
20
22
|
|
|
21
23
|
type CommandContextValue = {
|
|
@@ -51,7 +53,9 @@ export type CommandClearProps = ButtonHTMLAttributes<HTMLButtonElement>
|
|
|
51
53
|
export type CommandCloseProps = ComponentProps<typeof BaseDialog.Close>
|
|
52
54
|
export type CommandDividerProps = HTMLAttributes<HTMLDivElement>
|
|
53
55
|
export type CommandContentProps = HTMLAttributes<HTMLDivElement>
|
|
56
|
+
/** @deprecated Use FilterGroup for new filter controls. */
|
|
54
57
|
export type CommandFiltersProps = HTMLAttributes<HTMLUListElement>
|
|
58
|
+
/** @deprecated Use FilterItem for new filter controls. */
|
|
55
59
|
export type CommandFilterProps = ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
56
60
|
selected?: boolean
|
|
57
61
|
}
|
|
@@ -110,7 +114,7 @@ export const CommandBackdrop = ({
|
|
|
110
114
|
export const CommandPopup = ({ className, ...props }: CommandPopupProps) => (
|
|
111
115
|
<BaseDialog.Popup
|
|
112
116
|
className={cn(
|
|
113
|
-
"fixed top-1/2 left-1/2 z-50 flex h-[min(80vh,32rem)] w-
|
|
117
|
+
"fixed top-1/2 left-1/2 z-50 flex h-[min(80vh,32rem)] w-[calc(100vw-2rem)] max-w-3xl -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-2xl bg-surface-raised shadow-ambient outline-none",
|
|
114
118
|
motion.popupCenter,
|
|
115
119
|
className,
|
|
116
120
|
)}
|
|
@@ -175,9 +179,10 @@ export const CommandClear = ({
|
|
|
175
179
|
<button
|
|
176
180
|
type="button"
|
|
177
181
|
className={cn(
|
|
178
|
-
"inline-flex h-8 cursor-pointer items-center rounded-full border border-transparent px-3 text-sm text-fg-quaternary
|
|
182
|
+
"inline-flex h-8 cursor-pointer items-center rounded-full border border-transparent px-3 text-sm text-fg-quaternary",
|
|
179
183
|
motion.colors,
|
|
180
|
-
|
|
184
|
+
focusRing,
|
|
185
|
+
"hover:bg-background-tertiary hover:text-fg-primary data-[active=true]:text-fg-primary",
|
|
181
186
|
className,
|
|
182
187
|
)}
|
|
183
188
|
{...props}
|
|
@@ -204,9 +209,10 @@ export const CommandClose = ({
|
|
|
204
209
|
}: CommandCloseProps) => (
|
|
205
210
|
<BaseDialog.Close
|
|
206
211
|
className={cn(
|
|
207
|
-
"inline-flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-full border border-transparent text-fg-primary
|
|
212
|
+
"inline-flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-full border border-transparent text-fg-primary",
|
|
208
213
|
motion.colors,
|
|
209
|
-
|
|
214
|
+
focusRing,
|
|
215
|
+
"hover:bg-background-tertiary",
|
|
210
216
|
className,
|
|
211
217
|
)}
|
|
212
218
|
aria-label={props["aria-label"] ?? "Close"}
|
|
@@ -238,7 +244,8 @@ export const CommandFilters = ({
|
|
|
238
244
|
<ul
|
|
239
245
|
role="list"
|
|
240
246
|
className={cn(
|
|
241
|
-
|
|
247
|
+
filterGroupVariants({ variant: "pill" }),
|
|
248
|
+
"w-full shrink-0 justify-start py-1.5",
|
|
242
249
|
className,
|
|
243
250
|
)}
|
|
244
251
|
{...props}
|
|
@@ -256,11 +263,10 @@ export const CommandFilter = ({
|
|
|
256
263
|
type="button"
|
|
257
264
|
aria-pressed={selected}
|
|
258
265
|
data-selected={selected || undefined}
|
|
266
|
+
data-pressed={selected || undefined}
|
|
259
267
|
className={cn(
|
|
260
|
-
|
|
268
|
+
filterItemVariants({ variant: "pill", size: "md" }),
|
|
261
269
|
motion.colors,
|
|
262
|
-
"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",
|
|
263
|
-
"data-selected:bg-background-tertiary data-selected:text-fg-primary",
|
|
264
270
|
className,
|
|
265
271
|
)}
|
|
266
272
|
{...props}
|
|
@@ -278,7 +284,7 @@ export const CommandList = ({ className, ...props }: CommandListProps) => {
|
|
|
278
284
|
id={listboxId}
|
|
279
285
|
role="listbox"
|
|
280
286
|
className={cn(
|
|
281
|
-
"grid min-h-0 flex-1 content-start gap-0 overflow-y-auto overscroll-contain
|
|
287
|
+
"grid min-h-0 flex-1 content-start gap-0 overflow-y-auto overscroll-contain p-1",
|
|
282
288
|
className,
|
|
283
289
|
)}
|
|
284
290
|
{...props}
|
|
@@ -306,11 +312,11 @@ export const CommandItem = ({
|
|
|
306
312
|
}, [selected])
|
|
307
313
|
|
|
308
314
|
const itemClassName = cn(
|
|
309
|
-
"flex h-15 min-h-15 w-full cursor-pointer items-center justify-between gap-4 rounded-lg border border-transparent px-4 text-left text-sm font-medium text-fg-primary
|
|
315
|
+
"flex h-15 min-h-15 w-full cursor-pointer items-center justify-between gap-4 rounded-lg border border-transparent px-4 text-left text-sm font-medium text-fg-primary select-none",
|
|
310
316
|
motion.colors,
|
|
311
317
|
"hover:bg-background-tertiary data-selected:bg-background-tertiary",
|
|
312
318
|
"data-disabled:cursor-not-allowed data-disabled:opacity-50",
|
|
313
|
-
|
|
319
|
+
focusRing,
|
|
314
320
|
className,
|
|
315
321
|
)
|
|
316
322
|
|
|
@@ -381,6 +387,15 @@ export const CommandTitle = ({ className, children }: CommandTitleProps) => (
|
|
|
381
387
|
<span className={cn("min-w-0 truncate", className)}>{children}</span>
|
|
382
388
|
)
|
|
383
389
|
|
|
390
|
+
export type CommandMetaProps = HTMLAttributes<HTMLSpanElement>
|
|
391
|
+
|
|
392
|
+
export const CommandMeta = ({ className, ...props }: CommandMetaProps) => (
|
|
393
|
+
<span
|
|
394
|
+
className={cn("shrink-0 text-xs font-normal text-fg-tertiary", className)}
|
|
395
|
+
{...props}
|
|
396
|
+
/>
|
|
397
|
+
)
|
|
398
|
+
|
|
384
399
|
export const CommandDialogTitle = ({
|
|
385
400
|
className,
|
|
386
401
|
...props
|
package/src/dialog.tsx
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { Dialog as BaseDialog } from "@base-ui/react/dialog"
|
|
4
4
|
import type { ComponentProps, HTMLAttributes } from "react"
|
|
5
|
+
import { useRef } from "react"
|
|
5
6
|
import { cn } from "./lib/cn"
|
|
6
7
|
import { motion } from "./lib/motion"
|
|
7
8
|
|
|
@@ -41,16 +42,38 @@ export const DialogBackdrop = ({
|
|
|
41
42
|
/>
|
|
42
43
|
)
|
|
43
44
|
|
|
44
|
-
export const DialogPopup = ({
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
45
|
+
export const DialogPopup = ({
|
|
46
|
+
className,
|
|
47
|
+
initialFocus,
|
|
48
|
+
ref,
|
|
49
|
+
...props
|
|
50
|
+
}: DialogPopupProps) => {
|
|
51
|
+
const popupRef = useRef<HTMLDivElement | null>(null)
|
|
52
|
+
|
|
53
|
+
const setRef = (node: HTMLDivElement | null) => {
|
|
54
|
+
popupRef.current = node
|
|
55
|
+
if (typeof ref === "function") ref(node)
|
|
56
|
+
else if (ref) ref.current = node
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<BaseDialog.Popup
|
|
61
|
+
ref={setRef}
|
|
62
|
+
// Base UI focuses the first tabbable element by default. In a dialog long
|
|
63
|
+
// enough to scroll that is usually the action at the bottom, and focusing
|
|
64
|
+
// it scrolls the body to the end before the reader has seen the start —
|
|
65
|
+
// so the popup takes focus itself, which is what its `tabindex="-1"` is
|
|
66
|
+
// for. Callers can still name their own target.
|
|
67
|
+
initialFocus={initialFocus ?? popupRef}
|
|
68
|
+
className={cn(
|
|
69
|
+
"fixed top-1/2 left-1/2 z-50 flex w-full max-w-md -translate-x-1/2 -translate-y-1/2 flex-col gap-6 rounded-xl border border-border-primary bg-surface-raised p-5 shadow-md outline-none max-h-[calc(100dvh-2rem)] overflow-y-auto max-sm:max-w-[calc(100vw-2rem)]",
|
|
70
|
+
motion.popupCenter,
|
|
71
|
+
className,
|
|
72
|
+
)}
|
|
73
|
+
{...props}
|
|
74
|
+
/>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
54
77
|
|
|
55
78
|
export const DialogTitle = ({ className, ...props }: DialogTitleProps) => (
|
|
56
79
|
<BaseDialog.Title
|
package/src/empty.tsx
CHANGED
|
@@ -10,7 +10,7 @@ export type EmptyActionsProps = ComponentProps<"div">;
|
|
|
10
10
|
export const Empty = ({ className, ...props }: EmptyProps) => (
|
|
11
11
|
<div
|
|
12
12
|
className={cn(
|
|
13
|
-
"flex min-h-48 flex-col items-center justify-center
|
|
13
|
+
"flex min-h-48 flex-col items-center justify-center rounded-xl border border-dashed border-border-primary bg-surface p-8 text-center",
|
|
14
14
|
className,
|
|
15
15
|
)}
|
|
16
16
|
{...props}
|
|
@@ -20,7 +20,7 @@ export const Empty = ({ className, ...props }: EmptyProps) => (
|
|
|
20
20
|
export const EmptyIcon = ({ className, ...props }: EmptyIconProps) => (
|
|
21
21
|
<div
|
|
22
22
|
className={cn(
|
|
23
|
-
"flex size-10 items-center justify-center rounded-lg bg-background-tertiary text-fg-secondary [&_svg]:size-5",
|
|
23
|
+
"mb-4 flex size-10 items-center justify-center rounded-lg bg-background-tertiary text-fg-secondary [&_svg]:size-5",
|
|
24
24
|
className,
|
|
25
25
|
)}
|
|
26
26
|
{...props}
|
|
@@ -36,7 +36,7 @@ export const EmptyDescription = ({
|
|
|
36
36
|
...props
|
|
37
37
|
}: EmptyDescriptionProps) => (
|
|
38
38
|
<p
|
|
39
|
-
className={cn("max-w-sm text-sm text-fg-secondary", className)}
|
|
39
|
+
className={cn("mt-1 max-w-sm text-sm text-fg-secondary", className)}
|
|
40
40
|
{...props}
|
|
41
41
|
/>
|
|
42
42
|
);
|
|
@@ -44,7 +44,7 @@ export const EmptyDescription = ({
|
|
|
44
44
|
export const EmptyActions = ({ className, ...props }: EmptyActionsProps) => (
|
|
45
45
|
<div
|
|
46
46
|
className={cn(
|
|
47
|
-
"mt-
|
|
47
|
+
"mt-4 flex flex-wrap items-center justify-center gap-2",
|
|
48
48
|
className,
|
|
49
49
|
)}
|
|
50
50
|
{...props}
|
package/src/field.tsx
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { Field as BaseField } from "@base-ui/react/field"
|
|
4
4
|
import type { ComponentProps } from "react"
|
|
5
5
|
import { cn } from "./lib/cn"
|
|
6
|
+
import { focusRing, focusRingInvalid } from "./lib/focus"
|
|
6
7
|
|
|
7
8
|
export type FieldProps = ComponentProps<typeof BaseField.Root>
|
|
8
9
|
export type FieldLabelProps = ComponentProps<typeof BaseField.Label>
|
|
@@ -49,10 +50,10 @@ export const FieldError = ({ className, ...props }: FieldErrorProps) => (
|
|
|
49
50
|
export const FieldControl = ({ className, ...props }: FieldControlProps) => (
|
|
50
51
|
<BaseField.Control
|
|
51
52
|
className={cn(
|
|
52
|
-
"text-sm flex h-9 w-full cursor-text rounded-md border border-border-secondary bg-surface px-3 text-fg-primary inset-shadow-outline-top
|
|
53
|
-
|
|
53
|
+
"text-sm flex h-9 w-full cursor-text rounded-md border border-border-secondary bg-surface px-3 text-fg-primary inset-shadow-outline-top transition-[color,box-shadow] duration-[var(--duration-sm)] ease-enter placeholder:text-fg-quaternary",
|
|
54
|
+
focusRing,
|
|
55
|
+
focusRingInvalid,
|
|
54
56
|
"data-invalid:border-destructive data-invalid:focus-visible:border-destructive data-invalid:focus-visible:ring-destructive/20",
|
|
55
|
-
"aria-invalid:border-destructive aria-invalid:focus-visible:border-destructive aria-invalid:focus-visible:ring-destructive/20",
|
|
56
57
|
"data-disabled:cursor-not-allowed data-disabled:opacity-50",
|
|
57
58
|
className,
|
|
58
59
|
)}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { Toggle as BaseToggle } from "@base-ui/react/toggle"
|
|
4
|
+
import { ToggleGroup as BaseToggleGroup } from "@base-ui/react/toggle-group"
|
|
5
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
6
|
+
import { createContext, type ComponentProps, useContext } from "react"
|
|
7
|
+
import { cn } from "./lib/cn"
|
|
8
|
+
import { focusRing } from "./lib/focus"
|
|
9
|
+
import { motion } from "./lib/motion"
|
|
10
|
+
|
|
11
|
+
const filterGroupVariants = cva("flex w-fit items-center", {
|
|
12
|
+
variants: {
|
|
13
|
+
variant: {
|
|
14
|
+
pill: "flex-wrap gap-1",
|
|
15
|
+
segmented: "gap-0.5 rounded-lg bg-background-tertiary p-0.5",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
defaultVariants: {
|
|
19
|
+
variant: "pill",
|
|
20
|
+
},
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const filterItemVariants = cva(
|
|
24
|
+
cn(
|
|
25
|
+
"group/filter-item inline-flex shrink-0 cursor-pointer items-center justify-center gap-1.5 whitespace-nowrap border border-transparent text-fg-tertiary hover:text-fg-primary data-disabled:cursor-not-allowed data-disabled:opacity-50 data-pressed:text-fg-primary",
|
|
26
|
+
focusRing,
|
|
27
|
+
),
|
|
28
|
+
{
|
|
29
|
+
variants: {
|
|
30
|
+
variant: {
|
|
31
|
+
pill: "rounded-full hover:bg-background-tertiary data-pressed:bg-background-tertiary",
|
|
32
|
+
segmented:
|
|
33
|
+
"rounded-md hover:bg-background-quaternary data-pressed:bg-surface-raised data-pressed:shadow-hairline",
|
|
34
|
+
},
|
|
35
|
+
size: {
|
|
36
|
+
sm: "text-xs",
|
|
37
|
+
md: "text-sm",
|
|
38
|
+
lg: "text-sm",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
compoundVariants: [
|
|
42
|
+
{ variant: "pill", size: "sm", class: "h-8 px-3" },
|
|
43
|
+
{ variant: "pill", size: "md", class: "h-9 px-4" },
|
|
44
|
+
{ variant: "pill", size: "lg", class: "h-10 px-5" },
|
|
45
|
+
{ variant: "segmented", size: "sm", class: "h-7 px-2.5" },
|
|
46
|
+
{ variant: "segmented", size: "md", class: "h-8 px-3" },
|
|
47
|
+
{ variant: "segmented", size: "lg", class: "h-9 px-3.5" },
|
|
48
|
+
],
|
|
49
|
+
defaultVariants: {
|
|
50
|
+
variant: "pill",
|
|
51
|
+
size: "md",
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
export type FilterGroupVariant = NonNullable<
|
|
57
|
+
VariantProps<typeof filterGroupVariants>["variant"]
|
|
58
|
+
>
|
|
59
|
+
export type FilterGroupSize = NonNullable<
|
|
60
|
+
VariantProps<typeof filterItemVariants>["size"]
|
|
61
|
+
>
|
|
62
|
+
|
|
63
|
+
type FilterGroupStyleContextValue = {
|
|
64
|
+
variant: FilterGroupVariant
|
|
65
|
+
size: FilterGroupSize
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const FilterGroupStyleContext = createContext<FilterGroupStyleContextValue>({
|
|
69
|
+
variant: "pill",
|
|
70
|
+
size: "md",
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
export type FilterGroupProps = ComponentProps<typeof BaseToggleGroup> &
|
|
74
|
+
VariantProps<typeof filterGroupVariants> & {
|
|
75
|
+
/** Whether pressing the active item can clear a single-select group. */
|
|
76
|
+
allowEmpty?: boolean
|
|
77
|
+
size?: FilterGroupSize
|
|
78
|
+
}
|
|
79
|
+
export type FilterItemProps = ComponentProps<typeof BaseToggle> &
|
|
80
|
+
VariantProps<typeof filterItemVariants>
|
|
81
|
+
export type FilterCountProps = ComponentProps<"span">
|
|
82
|
+
|
|
83
|
+
export const FilterGroup = ({
|
|
84
|
+
className,
|
|
85
|
+
variant = "pill",
|
|
86
|
+
size = "md",
|
|
87
|
+
allowEmpty = false,
|
|
88
|
+
multiple = false,
|
|
89
|
+
onValueChange,
|
|
90
|
+
...props
|
|
91
|
+
}: FilterGroupProps) => (
|
|
92
|
+
<FilterGroupStyleContext.Provider value={{ variant: variant ?? "pill", size }}>
|
|
93
|
+
<BaseToggleGroup
|
|
94
|
+
className={cn(filterGroupVariants({ variant }), className)}
|
|
95
|
+
multiple={multiple}
|
|
96
|
+
onValueChange={(value, eventDetails) => {
|
|
97
|
+
if (!multiple && !allowEmpty && value.length === 0) {
|
|
98
|
+
eventDetails.cancel()
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
onValueChange?.(value, eventDetails)
|
|
102
|
+
}}
|
|
103
|
+
{...props}
|
|
104
|
+
/>
|
|
105
|
+
</FilterGroupStyleContext.Provider>
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
export const FilterItem = ({
|
|
109
|
+
className,
|
|
110
|
+
variant,
|
|
111
|
+
size,
|
|
112
|
+
type = "button",
|
|
113
|
+
...props
|
|
114
|
+
}: FilterItemProps) => {
|
|
115
|
+
const styles = useContext(FilterGroupStyleContext)
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<BaseToggle
|
|
119
|
+
type={type}
|
|
120
|
+
className={cn(
|
|
121
|
+
filterItemVariants({
|
|
122
|
+
variant: variant ?? styles.variant,
|
|
123
|
+
size: size ?? styles.size,
|
|
124
|
+
}),
|
|
125
|
+
motion.colors,
|
|
126
|
+
className,
|
|
127
|
+
)}
|
|
128
|
+
{...props}
|
|
129
|
+
/>
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export const FilterCount = ({ className, ...props }: FilterCountProps) => (
|
|
134
|
+
<span
|
|
135
|
+
className={cn(
|
|
136
|
+
"text-2xs-strong inline-flex min-w-4 items-center justify-center rounded-full bg-background-quaternary px-1 py-0.5 text-fg-secondary tabular-nums group-data-pressed/filter-item:bg-surface",
|
|
137
|
+
className,
|
|
138
|
+
)}
|
|
139
|
+
{...props}
|
|
140
|
+
/>
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
export { filterGroupVariants, filterItemVariants }
|
package/src/icons.tsx
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import type { CentralIconBaseProps } from "@central-icons-react/round-outlined-radius-2-stroke-2/CentralIconBase"
|
|
4
|
+
import { IconArrowDown as IconArrowDownBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconArrowDown"
|
|
4
5
|
import { IconBell as IconBellBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconBell"
|
|
5
6
|
import { IconCalendar1 as IconCalendar1Base } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconCalendar1"
|
|
6
7
|
import { IconChainLink1 as IconChainLink1Base } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconChainLink1"
|
|
8
|
+
import { IconChainLink3 as IconChainLink3Base } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconChainLink3"
|
|
7
9
|
import { IconCheckmark1 as IconCheckmark1Base } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconCheckmark1"
|
|
8
10
|
import { IconChevronBottom as IconChevronBottomBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconChevronBottom"
|
|
9
11
|
import { IconChevronDownSmall as IconChevronDownSmallBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconChevronDownSmall"
|
|
@@ -13,18 +15,26 @@ import { IconCircleInfo as IconCircleInfoBase } from "@central-icons-react/round
|
|
|
13
15
|
import { IconClipboard as IconClipboardBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconClipboard"
|
|
14
16
|
import { IconCrossSmall as IconCrossSmallBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconCrossSmall"
|
|
15
17
|
import { IconDotGrid1x3Horizontal as IconDotGrid1x3HorizontalBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconDotGrid1x3Horizontal"
|
|
18
|
+
import { IconDotGrid2x3 as IconDotGrid2x3Base } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconDotGrid2x3"
|
|
16
19
|
import { IconExclamationCircle as IconExclamationCircleBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconExclamationCircle"
|
|
17
20
|
import { IconExclamationTriangle as IconExclamationTriangleBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconExclamationTriangle"
|
|
18
21
|
import { IconHome as IconHomeBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconHome"
|
|
22
|
+
import { IconFullScreen as IconFullScreenBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconFullScreen"
|
|
23
|
+
import { IconHighlight as IconHighlightBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconHighlight"
|
|
19
24
|
import { IconMagnifyingGlass as IconMagnifyingGlassBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconMagnifyingGlass"
|
|
20
25
|
import { IconMinusMedium as IconMinusBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconMinusMedium"
|
|
26
|
+
import { IconPause as IconPauseBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconPause"
|
|
21
27
|
import { IconMoon as IconMoonBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconMoon"
|
|
22
28
|
import { IconPeople as IconPeopleBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconPeople"
|
|
29
|
+
import { IconPlay as IconPlayBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconPlay"
|
|
23
30
|
import { IconPlusMedium as IconPlusBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconPlusMedium"
|
|
24
31
|
import { IconSettingsGear1 as IconSettingsGear1Base } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconSettingsGear1"
|
|
25
32
|
import { IconSquareBehindSquare6 as IconSquareBehindSquare6Base } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconSquareBehindSquare6"
|
|
26
33
|
import { IconStar as IconStarBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconStar"
|
|
34
|
+
import { IconStrikeThrough as IconStrikeThroughBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconStrikeThrough"
|
|
27
35
|
import { IconSun as IconSunBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconSun"
|
|
36
|
+
import { IconVolumeFull as IconVolumeFullBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconVolumeFull"
|
|
37
|
+
import { IconVolumeOff as IconVolumeOffBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconVolumeOff"
|
|
28
38
|
import { IconX as IconXBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconX"
|
|
29
39
|
import type { ComponentType } from "react"
|
|
30
40
|
|
|
@@ -42,16 +52,21 @@ export const withCentralIconDefaults = (
|
|
|
42
52
|
): CentralIconComponent => {
|
|
43
53
|
const CentralIcon = ({
|
|
44
54
|
size = CENTRAL_ICON_SIZE,
|
|
55
|
+
// Central's "masked" mode hardcodes one maskId per icon name. A second
|
|
56
|
+
// instance on the page paints as a solid square. Raw mode avoids that.
|
|
57
|
+
mode = "raw",
|
|
45
58
|
...props
|
|
46
|
-
}: CentralIconProps) => <Icon size={size} {
|
|
59
|
+
}: CentralIconProps) => <Icon {...props} size={size} mode={mode} />
|
|
47
60
|
|
|
48
61
|
CentralIcon.displayName = Icon.displayName ?? Icon.name
|
|
49
62
|
return CentralIcon
|
|
50
63
|
}
|
|
51
64
|
|
|
65
|
+
export const IconArrowDown = withCentralIconDefaults(IconArrowDownBase)
|
|
52
66
|
export const IconBell = withCentralIconDefaults(IconBellBase)
|
|
53
67
|
export const IconCalendar1 = withCentralIconDefaults(IconCalendar1Base)
|
|
54
68
|
export const IconChainLink1 = withCentralIconDefaults(IconChainLink1Base)
|
|
69
|
+
export const IconChainLink3 = withCentralIconDefaults(IconChainLink3Base)
|
|
55
70
|
export const IconCheckmark1 = withCentralIconDefaults(IconCheckmark1Base)
|
|
56
71
|
export const IconChevronBottom = withCentralIconDefaults(IconChevronBottomBase)
|
|
57
72
|
export const IconChevronDownSmall = withCentralIconDefaults(
|
|
@@ -67,26 +82,34 @@ export const IconCrossSmall = withCentralIconDefaults(IconCrossSmallBase)
|
|
|
67
82
|
export const IconDotGrid1x3Horizontal = withCentralIconDefaults(
|
|
68
83
|
IconDotGrid1x3HorizontalBase,
|
|
69
84
|
)
|
|
85
|
+
export const IconDotGrid2x3 = withCentralIconDefaults(IconDotGrid2x3Base)
|
|
70
86
|
export const IconExclamationCircle = withCentralIconDefaults(
|
|
71
87
|
IconExclamationCircleBase,
|
|
72
88
|
)
|
|
73
89
|
export const IconExclamationTriangle = withCentralIconDefaults(
|
|
74
90
|
IconExclamationTriangleBase,
|
|
75
91
|
)
|
|
92
|
+
export const IconFullScreen = withCentralIconDefaults(IconFullScreenBase)
|
|
76
93
|
export const IconHome = withCentralIconDefaults(IconHomeBase)
|
|
77
94
|
export const IconMagnifyingGlass = withCentralIconDefaults(
|
|
78
95
|
IconMagnifyingGlassBase,
|
|
79
96
|
)
|
|
80
97
|
export const IconMinus = withCentralIconDefaults(IconMinusBase)
|
|
98
|
+
export const IconPause = withCentralIconDefaults(IconPauseBase)
|
|
81
99
|
export const IconMoon = withCentralIconDefaults(IconMoonBase)
|
|
82
100
|
export const IconPeople = withCentralIconDefaults(IconPeopleBase)
|
|
101
|
+
export const IconPlay = withCentralIconDefaults(IconPlayBase)
|
|
83
102
|
export const IconPlus = withCentralIconDefaults(IconPlusBase)
|
|
84
103
|
export const IconSettingsGear1 = withCentralIconDefaults(IconSettingsGear1Base)
|
|
85
104
|
export const IconSquareBehindSquare6 = withCentralIconDefaults(
|
|
86
105
|
IconSquareBehindSquare6Base,
|
|
87
106
|
)
|
|
88
107
|
export const IconStar = withCentralIconDefaults(IconStarBase)
|
|
108
|
+
export const IconStrikeThrough = withCentralIconDefaults(IconStrikeThroughBase)
|
|
109
|
+
export const IconHighlight = withCentralIconDefaults(IconHighlightBase)
|
|
89
110
|
export const IconSun = withCentralIconDefaults(IconSunBase)
|
|
111
|
+
export const IconVolumeFull = withCentralIconDefaults(IconVolumeFullBase)
|
|
112
|
+
export const IconVolumeOff = withCentralIconDefaults(IconVolumeOffBase)
|
|
90
113
|
export const IconX = withCentralIconDefaults(IconXBase)
|
|
91
114
|
|
|
92
115
|
export const iconGallery = [
|
|
@@ -110,9 +133,13 @@ export const iconGallery = [
|
|
|
110
133
|
{ name: "IconCircleInfo", Icon: IconCircleInfo },
|
|
111
134
|
{ name: "IconClipboard", Icon: IconClipboard },
|
|
112
135
|
{ name: "IconDotGrid1x3Horizontal", Icon: IconDotGrid1x3Horizontal },
|
|
136
|
+
{ name: "IconDotGrid2x3", Icon: IconDotGrid2x3 },
|
|
113
137
|
{ name: "IconSquareBehindSquare6", Icon: IconSquareBehindSquare6 },
|
|
114
138
|
{ name: "IconChainLink1", Icon: IconChainLink1 },
|
|
139
|
+
{ name: "IconChainLink3", Icon: IconChainLink3 },
|
|
115
140
|
{ name: "IconExclamationCircle", Icon: IconExclamationCircle },
|
|
116
141
|
{ name: "IconExclamationTriangle", Icon: IconExclamationTriangle },
|
|
117
142
|
{ name: "IconStar", Icon: IconStar },
|
|
143
|
+
{ name: "IconStrikeThrough", Icon: IconStrikeThrough },
|
|
144
|
+
{ name: "IconHighlight", Icon: IconHighlight },
|
|
118
145
|
] as const
|