@passui/react 0.1.44 → 0.1.46

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.
Files changed (140) hide show
  1. package/README.md +57 -46
  2. package/dist/types/src/components/accordion/accordion.d.ts +1 -1
  3. package/dist/types/src/components/breadcrumb/breadcrumb.d.ts +1 -1
  4. package/dist/types/src/components/inline-disclosure/inline-disclosure.d.ts +1 -1
  5. package/dist/types/src/components/popover/anchored-surface.d.ts +5 -1
  6. package/dist/types/src/components/popover/index.d.ts +41 -0
  7. package/dist/types/src/components/popover/popover-content.d.ts +32 -0
  8. package/dist/types/src/components/popover/popover-root.d.ts +22 -0
  9. package/dist/types/src/components/popover/popover-trigger.d.ts +23 -0
  10. package/dist/types/src/components/stepper/rolling-digit.d.ts +1 -1
  11. package/dist/types/src/components/table/table.d.ts +1 -1
  12. package/dist/types/src/components/view-controller/view-controller.d.ts +2 -2
  13. package/dist/types/src/index.d.ts +1 -1
  14. package/dist/types/src/machines/popover.machine.d.ts +30 -0
  15. package/package.json +307 -307
  16. package/src/appearance/index.ts +2 -2
  17. package/src/appearance/platform.tsx +76 -60
  18. package/src/appearance/theme.tsx +409 -377
  19. package/src/components/accordion/accordion.tsx +58 -49
  20. package/src/components/ambient/ambient.tsx +25 -25
  21. package/src/components/ambient/index.ts +1 -1
  22. package/src/components/aspect-ratio/aspect-ratio.tsx +21 -16
  23. package/src/components/breadcrumb/breadcrumb.tsx +63 -57
  24. package/src/components/button/button-styles.ts +39 -32
  25. package/src/components/button/button.tsx +91 -84
  26. package/src/components/button/icon-button.tsx +27 -25
  27. package/src/components/button/pending-spinner.tsx +8 -8
  28. package/src/components/button/use-pending-indicator.ts +11 -11
  29. package/src/components/chip/chip.tsx +35 -30
  30. package/src/components/code-block/code-block.tsx +40 -39
  31. package/src/components/combo-box/combo-box.tsx +5 -5
  32. package/src/components/command/command.tsx +124 -94
  33. package/src/components/compact-date-picker/calendar-month.ts +75 -53
  34. package/src/components/compact-date-picker/compact-date-picker.tsx +308 -289
  35. package/src/components/compact-time-picker/compact-time-picker.tsx +288 -281
  36. package/src/components/compact-time-picker/time-value.ts +32 -29
  37. package/src/components/content-unavailable-view/content-unavailable-view.tsx +64 -48
  38. package/src/components/content-view/content-view.tsx +168 -159
  39. package/src/components/content-view/index.ts +1 -1
  40. package/src/components/context-menu/conceal-menu-source.ts +25 -25
  41. package/src/components/context-menu/context-menu-host.tsx +439 -398
  42. package/src/components/context-menu/context-menu-store.ts +125 -123
  43. package/src/components/context-menu/menu-navigation.ts +25 -18
  44. package/src/components/context-menu/menu-position.ts +33 -29
  45. package/src/components/context-menu/menu-reveal.ts +32 -32
  46. package/src/components/context-menu/use-context-menu.ts +63 -56
  47. package/src/components/conversation/conversation.tsx +265 -263
  48. package/src/components/conversation/index.ts +1 -1
  49. package/src/components/date-picker/date-picker.tsx +95 -94
  50. package/src/components/date-picker/date-value.ts +20 -10
  51. package/src/components/dialog/dialog-host.tsx +105 -103
  52. package/src/components/dialog/dialog-store.ts +37 -36
  53. package/src/components/dialog/dialog.tsx +192 -179
  54. package/src/components/dialog/index.ts +1 -1
  55. package/src/components/disclosure-group/disclosure-group.tsx +93 -86
  56. package/src/components/divider/divider.tsx +24 -22
  57. package/src/components/inline-disclosure/inline-disclosure.tsx +40 -36
  58. package/src/components/keyboard-shortcut/keyboard-shortcut.tsx +23 -19
  59. package/src/components/labeled-content/labeled-content.tsx +68 -68
  60. package/src/components/locale-switcher/index.ts +1 -1
  61. package/src/components/locale-switcher/locale-switcher.tsx +279 -251
  62. package/src/components/menu/menu.tsx +75 -58
  63. package/src/components/morph/index.ts +2 -2
  64. package/src/components/morph/morph.tsx +227 -201
  65. package/src/components/morph/use-morph-disclosure.ts +19 -19
  66. package/src/components/one-time-code/one-time-code.tsx +216 -200
  67. package/src/components/picker/picker-trigger.ts +16 -13
  68. package/src/components/picker/picker.tsx +371 -345
  69. package/src/components/popover/anchored-surface.tsx +253 -229
  70. package/src/components/popover/index.ts +50 -0
  71. package/src/components/popover/popover-content.tsx +334 -0
  72. package/src/components/popover/popover-fit.ts +58 -53
  73. package/src/components/popover/popover-root.tsx +97 -0
  74. package/src/components/popover/popover-trigger.tsx +71 -0
  75. package/src/components/profile-image/profile-image.tsx +80 -64
  76. package/src/components/progress-view/progress-view.tsx +61 -59
  77. package/src/components/progress-view/use-liquid-progress.ts +38 -38
  78. package/src/components/radio-group/radio-group.tsx +71 -68
  79. package/src/components/search-field/expanding-search-field.tsx +191 -177
  80. package/src/components/search-field/search-field.tsx +59 -50
  81. package/src/components/search-list/search-list.tsx +246 -236
  82. package/src/components/section/section.tsx +58 -58
  83. package/src/components/secure-field/secure-field.tsx +3 -3
  84. package/src/components/segmented-control/segmented-control.tsx +136 -136
  85. package/src/components/sheet/modal-environment.ts +71 -64
  86. package/src/components/sheet/sheet-portal.tsx +58 -53
  87. package/src/components/sheet/sheet.tsx +146 -141
  88. package/src/components/sheet/use-sheet-focus.ts +97 -82
  89. package/src/components/sidebar/sidebar.tsx +78 -78
  90. package/src/components/skeleton/skeleton.tsx +30 -21
  91. package/src/components/slider/slider.tsx +113 -104
  92. package/src/components/slosh-slider/liquid-step.ts +32 -31
  93. package/src/components/slosh-slider/slosh-slider.tsx +265 -228
  94. package/src/components/stat-tile/index.ts +1 -1
  95. package/src/components/stat-tile/stat-tile.tsx +79 -68
  96. package/src/components/status-indicator/status-indicator.tsx +40 -37
  97. package/src/components/stepper/rolling-digit.tsx +82 -68
  98. package/src/components/stepper/stepper.tsx +122 -113
  99. package/src/components/swatch-picker/swatch-picker.tsx +120 -120
  100. package/src/components/tab-bar/tab-bar.tsx +149 -148
  101. package/src/components/tab-view/tab-view.tsx +140 -137
  102. package/src/components/table/table.tsx +117 -98
  103. package/src/components/text-editor/text-editor.tsx +34 -29
  104. package/src/components/text-field/text-field.tsx +182 -180
  105. package/src/components/toast/toast-store.ts +127 -115
  106. package/src/components/toast/toaster.tsx +361 -347
  107. package/src/components/toggle/toggle.tsx +297 -282
  108. package/src/components/tooltip/tooltip-host.tsx +157 -150
  109. package/src/components/view-controller/index.ts +1 -1
  110. package/src/components/view-controller/view-controller.tsx +64 -53
  111. package/src/components/wake-slider/wake-slider.tsx +220 -205
  112. package/src/hooks/scroll/minimize-decision.ts +11 -7
  113. package/src/hooks/use-anchored-disclosure.ts +19 -19
  114. package/src/hooks/use-anchored-placement.ts +29 -25
  115. package/src/hooks/use-dismiss.ts +33 -29
  116. package/src/hooks/use-floating-dismiss.ts +24 -22
  117. package/src/hooks/use-media-query.ts +67 -60
  118. package/src/hooks/use-minimize-on-scroll.ts +69 -60
  119. package/src/hooks/use-visual-viewport.ts +63 -63
  120. package/src/hooks/viewport/types.ts +16 -16
  121. package/src/hooks/viewport/viewport-metrics.ts +16 -10
  122. package/src/icons/icons.tsx +440 -328
  123. package/src/index.ts +91 -91
  124. package/src/lib/anchored-placement.ts +70 -65
  125. package/src/lib/class-names.ts +14 -13
  126. package/src/lib/clipboard.ts +8 -7
  127. package/src/lib/date.ts +61 -60
  128. package/src/lib/floating-layers.ts +39 -24
  129. package/src/lib/haptics.ts +10 -10
  130. package/src/lib/language.ts +9 -9
  131. package/src/lib/range-value.ts +27 -21
  132. package/src/lib/screen-edges.ts +33 -30
  133. package/src/lib/shapes.ts +38 -34
  134. package/src/machines/popover.machine.ts +51 -0
  135. package/src/motion/transitions.ts +46 -43
  136. package/src/styles/code-block.css +69 -43
  137. package/src/styles/motion-controls.css +393 -191
  138. package/src/tokens.ts +1 -1
  139. package/dist/types/src/components/popover/popover.d.ts +0 -21
  140. package/src/components/popover/popover.tsx +0 -322
@@ -1,266 +1,276 @@
1
1
  import {
2
- createContext,
3
- useCallback,
4
- useContext,
5
- useId,
6
- useRef,
7
- useState,
8
- useEffect,
9
- type ComponentProps,
10
- } from "react"
11
- import { cn } from "../../lib/class-names"
2
+ createContext,
3
+ useCallback,
4
+ useContext,
5
+ useId,
6
+ useRef,
7
+ useState,
8
+ useEffect,
9
+ type ComponentProps,
10
+ } from "react";
11
+ import { cn } from "../../lib/class-names";
12
12
 
13
- export type SearchListSize = "sm" | "md" | "lg"
13
+ export type SearchListSize = "sm" | "md" | "lg";
14
14
 
15
15
  const fieldSizes: Record<SearchListSize, string> = {
16
- sm: "min-h-8 w-full rounded-full border border-separator bg-fill/60 px-3 text-caption text-label outline-none focus-visible:ring-2 focus-visible:ring-accent-text/50",
17
- md: "min-h-11 w-full rounded-full border border-separator bg-fill/60 px-4 text-subheadline text-label outline-none focus-visible:ring-2 focus-visible:ring-accent-text/50",
18
- lg: "min-h-13 w-full rounded-full border border-separator bg-fill/60 px-5 text-body text-label outline-none focus-visible:ring-2 focus-visible:ring-accent-text/50",
19
- }
16
+ sm: "min-h-8 w-full rounded-full border border-separator bg-fill/60 px-3 text-caption text-label outline-none focus-visible:ring-2 focus-visible:ring-accent-text/50",
17
+ md: "min-h-11 w-full rounded-full border border-separator bg-fill/60 px-4 text-subheadline text-label outline-none focus-visible:ring-2 focus-visible:ring-accent-text/50",
18
+ lg: "min-h-13 w-full rounded-full border border-separator bg-fill/60 px-5 text-body text-label outline-none focus-visible:ring-2 focus-visible:ring-accent-text/50",
19
+ };
20
20
  const headingSizes: Record<SearchListSize, string> = {
21
- sm: "px-1.5 py-1.5 text-caption2 font-medium text-label-tertiary",
22
- md: "px-2 py-2 text-caption font-medium text-label-tertiary",
23
- lg: "px-2.5 py-2.5 text-subheadline font-medium text-label-tertiary",
24
- }
21
+ sm: "px-1.5 py-1.5 text-caption2 font-medium text-label-tertiary",
22
+ md: "px-2 py-2 text-caption font-medium text-label-tertiary",
23
+ lg: "px-2.5 py-2.5 text-subheadline font-medium text-label-tertiary",
24
+ };
25
25
  const itemSizes: Record<SearchListSize, string> = {
26
- sm: "flex min-h-8 w-full items-center gap-1.5 rounded-md px-2 py-1 text-left text-footnote text-label hover:bg-fill aria-selected:bg-fill [@media(pointer:coarse)]:min-h-10",
27
- md: "flex min-h-9 w-full items-center gap-2 rounded-lg px-2.5 py-1.5 text-left text-subheadline text-label hover:bg-fill aria-selected:bg-fill [@media(pointer:coarse)]:min-h-11",
28
- lg: "flex min-h-11 w-full items-center gap-2.5 rounded-lg px-3 py-2 text-left text-body text-label hover:bg-fill aria-selected:bg-fill [@media(pointer:coarse)]:min-h-13",
29
- }
26
+ sm: "flex min-h-8 w-full items-center gap-1.5 rounded-md px-2 py-1 text-left text-footnote text-label hover:bg-fill aria-selected:bg-fill [@media(pointer:coarse)]:min-h-10",
27
+ md: "flex min-h-9 w-full items-center gap-2 rounded-lg px-2.5 py-1.5 text-left text-subheadline text-label hover:bg-fill aria-selected:bg-fill [@media(pointer:coarse)]:min-h-11",
28
+ lg: "flex min-h-11 w-full items-center gap-2.5 rounded-lg px-3 py-2 text-left text-body text-label hover:bg-fill aria-selected:bg-fill [@media(pointer:coarse)]:min-h-13",
29
+ };
30
30
 
31
31
  type SearchState = {
32
- query: string
33
- setQuery: (query: string) => void
34
- active: string
35
- listId: string
36
- open: boolean
37
- visible: number
38
- register: (id: string, visible: boolean | null) => void
39
- size: SearchListSize
40
- }
41
- const SearchContext = createContext<SearchState | null>(null)
32
+ query: string;
33
+ setQuery: (query: string) => void;
34
+ active: string;
35
+ listId: string;
36
+ open: boolean;
37
+ visible: number;
38
+ register: (id: string, visible: boolean | null) => void;
39
+ size: SearchListSize;
40
+ };
41
+ const SearchContext = createContext<SearchState | null>(null);
42
42
  function useSearch() {
43
- const search = useContext(SearchContext)
44
- if (!search) throw new Error("Search list controls need a SearchList parent")
45
- return search
43
+ const search = useContext(SearchContext);
44
+ if (!search) throw new Error("Search list controls need a SearchList parent");
45
+ return search;
46
46
  }
47
47
 
48
48
  export function SearchList({
49
- loop = true,
50
- open = true,
51
- size = "md",
52
- onKeyDown,
53
- className,
54
- ...props
49
+ loop = true,
50
+ open = true,
51
+ size = "md",
52
+ onKeyDown,
53
+ className,
54
+ ...props
55
55
  }: ComponentProps<"div"> & {
56
- loop?: boolean
57
- open?: boolean
58
- /** Field, item and heading density. @default "md" */
59
- size?: SearchListSize
56
+ loop?: boolean;
57
+ open?: boolean;
58
+ /** Field, item and heading density. @default "md" */
59
+ size?: SearchListSize;
60
60
  }) {
61
- const [query, setQuery] = useState("")
62
- const updateQuery = useCallback((next: string) => {
63
- setQuery(next)
64
- setActive("")
65
- }, [])
66
- const [active, setActive] = useState("")
67
- const [results, setResults] = useState<Record<string, boolean>>({})
68
- const listId = useId()
69
- const ref = useRef<HTMLDivElement>(null)
70
- const register = useCallback(
71
- (id: string, visible: boolean | null) =>
72
- setResults((previous) => {
73
- if (visible === null) {
74
- const next = { ...previous }
75
- delete next[id]
76
- return next
77
- }
78
- if (previous[id] === visible) return previous
79
- return { ...previous, [id]: visible }
80
- }),
81
- []
82
- )
83
- useEffect(() => {
84
- if (!open) return
85
- const available = Object.keys(results).filter((id) => results[id])
86
- if (!available.includes(active)) setActive(available[0] ?? "")
87
- }, [open, results, active])
88
- return (
89
- <SearchContext
90
- value={{
91
- query,
92
- setQuery: updateQuery,
93
- active,
94
- listId,
95
- open,
96
- visible: Object.values(results).filter(Boolean).length,
97
- register,
98
- size,
99
- }}
100
- >
101
- <div
102
- {...props}
103
- ref={ref}
104
- className={cn("relative", className)}
105
- onKeyDown={(event) => {
106
- onKeyDown?.(event)
107
- if (event.defaultPrevented || !open || event.nativeEvent.isComposing) return
108
- const items = Array.from(
109
- ref.current?.querySelectorAll<HTMLButtonElement>(
110
- "[data-search-result]:not([hidden]):not(:disabled)"
111
- ) ?? []
112
- )
113
- if (!items.length || event.altKey || event.ctrlKey || event.metaKey) return
114
- const index = items.findIndex((item) => item.id === active)
115
- if (event.key === "Enter") {
116
- if (
117
- event.target instanceof HTMLElement &&
118
- event.target.closest("button, a")
119
- )
120
- return
121
- event.preventDefault()
122
- ;(items[index] ?? items[0])?.click()
123
- return
124
- }
125
- if (event.key !== "ArrowDown" && event.key !== "ArrowUp") return
126
- event.preventDefault()
127
- const delta = event.key === "ArrowDown" ? 1 : -1
128
- const next =
129
- index < 0
130
- ? delta === 1
131
- ? 0
132
- : items.length - 1
133
- : loop
134
- ? (index + delta + items.length) % items.length
135
- : Math.max(0, Math.min(items.length - 1, index + delta))
136
- const item = items[next]
137
- if (!item) return
138
- setActive(item.id)
139
- item.scrollIntoView({
140
- block: "nearest",
141
- })
142
- }}
143
- />
144
- </SearchContext>
145
- )
61
+ const [query, setQuery] = useState("");
62
+ const updateQuery = useCallback((next: string) => {
63
+ setQuery(next);
64
+ setActive("");
65
+ }, []);
66
+ const [active, setActive] = useState("");
67
+ const [results, setResults] = useState<Record<string, boolean>>({});
68
+ const listId = useId();
69
+ const ref = useRef<HTMLDivElement>(null);
70
+ const register = useCallback(
71
+ (id: string, visible: boolean | null) =>
72
+ setResults((previous) => {
73
+ if (visible === null) {
74
+ const next = { ...previous };
75
+ delete next[id];
76
+ return next;
77
+ }
78
+ if (previous[id] === visible) return previous;
79
+ return { ...previous, [id]: visible };
80
+ }),
81
+ [],
82
+ );
83
+ useEffect(() => {
84
+ if (!open) return;
85
+ const available = Object.keys(results).filter((id) => results[id]);
86
+ if (!available.includes(active)) setActive(available[0] ?? "");
87
+ }, [open, results, active]);
88
+ return (
89
+ <SearchContext
90
+ value={{
91
+ query,
92
+ setQuery: updateQuery,
93
+ active,
94
+ listId,
95
+ open,
96
+ visible: Object.values(results).filter(Boolean).length,
97
+ register,
98
+ size,
99
+ }}
100
+ >
101
+ <div
102
+ {...props}
103
+ ref={ref}
104
+ role="presentation"
105
+ className={cn("relative", className)}
106
+ onKeyDown={(event) => {
107
+ onKeyDown?.(event);
108
+ if (event.defaultPrevented || !open || event.nativeEvent.isComposing)
109
+ return;
110
+ const items = Array.from(
111
+ ref.current?.querySelectorAll<HTMLButtonElement>(
112
+ "[data-search-result]:not([hidden]):not(:disabled)",
113
+ ) ?? [],
114
+ );
115
+ if (!items.length || event.altKey || event.ctrlKey || event.metaKey)
116
+ return;
117
+ const index = items.findIndex((item) => item.id === active);
118
+ if (event.key === "Enter") {
119
+ if (
120
+ event.target instanceof HTMLElement &&
121
+ event.target.closest("button, a")
122
+ )
123
+ return;
124
+ event.preventDefault();
125
+ (items[index] ?? items[0])?.click();
126
+ return;
127
+ }
128
+ if (event.key !== "ArrowDown" && event.key !== "ArrowUp") return;
129
+ event.preventDefault();
130
+ const delta = event.key === "ArrowDown" ? 1 : -1;
131
+ const next =
132
+ index < 0
133
+ ? delta === 1
134
+ ? 0
135
+ : items.length - 1
136
+ : loop
137
+ ? (index + delta + items.length) % items.length
138
+ : Math.max(0, Math.min(items.length - 1, index + delta));
139
+ const item = items[next];
140
+ if (!item) return;
141
+ setActive(item.id);
142
+ item.scrollIntoView({
143
+ block: "nearest",
144
+ });
145
+ }}
146
+ />
147
+ </SearchContext>
148
+ );
146
149
  }
147
150
  export function SearchListField({
148
- value,
149
- onValueChange,
150
- className,
151
- ...props
151
+ value,
152
+ onValueChange,
153
+ className,
154
+ ...props
152
155
  }: Omit<ComponentProps<"input">, "value" | "onChange"> & {
153
- value?: string
154
- onValueChange?: (value: string) => void
156
+ value?: string;
157
+ onValueChange?: (value: string) => void;
155
158
  }) {
156
- const search = useSearch()
157
- useEffect(() => {
158
- if (value !== undefined) search.setQuery(value)
159
- }, [value, search.setQuery])
160
- return (
161
- <input
162
- {...props}
163
- role="combobox"
164
- aria-autocomplete="list"
165
- aria-expanded={search.open}
166
- aria-controls={search.open ? search.listId : undefined}
167
- aria-activedescendant={search.open && search.active ? search.active : undefined}
168
- aria-label={props["aria-label"] ?? props.placeholder}
169
- value={value ?? search.query}
170
- onChange={(event) => {
171
- search.setQuery(event.target.value)
172
- onValueChange?.(event.target.value)
173
- }}
174
- className={cn(fieldSizes[search.size], className)}
175
- />
176
- )
159
+ const search = useSearch();
160
+ useEffect(() => {
161
+ if (value !== undefined) search.setQuery(value);
162
+ }, [value, search.setQuery]);
163
+ return (
164
+ <input
165
+ {...props}
166
+ role="combobox"
167
+ aria-autocomplete="list"
168
+ aria-expanded={search.open}
169
+ aria-controls={search.open ? search.listId : undefined}
170
+ aria-activedescendant={
171
+ search.open && search.active ? search.active : undefined
172
+ }
173
+ aria-label={props["aria-label"] ?? props.placeholder}
174
+ value={value ?? search.query}
175
+ onChange={(event) => {
176
+ search.setQuery(event.target.value);
177
+ onValueChange?.(event.target.value);
178
+ }}
179
+ className={cn(fieldSizes[search.size], className)}
180
+ />
181
+ );
177
182
  }
178
- export function SearchListResults({ className, ...props }: ComponentProps<"div">) {
179
- const search = useSearch()
180
- return (
181
- <div
182
- {...props}
183
- id={search.listId}
184
- role="listbox"
185
- hidden={!search.open}
186
- className={cn("overflow-y-auto p-1", className)}
187
- />
188
- )
183
+ export function SearchListResults({
184
+ className,
185
+ ...props
186
+ }: ComponentProps<"div">) {
187
+ const search = useSearch();
188
+ return (
189
+ <div
190
+ {...props}
191
+ id={search.listId}
192
+ role="listbox"
193
+ hidden={!search.open}
194
+ className={cn("overflow-y-auto p-1", className)}
195
+ />
196
+ );
189
197
  }
190
198
  export function SearchListEmpty(props: ComponentProps<"div">) {
191
- const search = useSearch()
192
- return search.visible ? null : <div {...props} role="status" />
199
+ const search = useSearch();
200
+ return search.visible ? null : <div {...props} role="status" />;
193
201
  }
194
202
  export function SearchListGroup({
195
- heading,
196
- children,
197
- className,
198
- ...props
203
+ heading,
204
+ children,
205
+ className,
206
+ ...props
199
207
  }: ComponentProps<"div"> & {
200
- heading?: React.ReactNode
208
+ heading?: React.ReactNode;
201
209
  }) {
202
- const search = useSearch()
203
- const id = useId()
204
- return (
205
- // biome-ignore lint/a11y/useSemanticElements: Listbox option groups are not form fieldsets.
206
- <div
207
- {...props}
208
- role="group"
209
- className={cn("not-has-[[data-search-result]:not([hidden])]:hidden", className)}
210
- aria-labelledby={heading ? id : undefined}
211
- >
212
- {heading && (
213
- <div id={id} className={headingSizes[search.size]}>
214
- {heading}
215
- </div>
216
- )}
217
- {children}
218
- </div>
219
- )
210
+ const search = useSearch();
211
+ const id = useId();
212
+ return (
213
+ <div
214
+ {...props}
215
+ role="group"
216
+ className={cn(
217
+ "not-has-[[data-search-result]:not([hidden])]:hidden",
218
+ className,
219
+ )}
220
+ aria-labelledby={heading ? id : undefined}
221
+ >
222
+ {heading && (
223
+ <div id={id} className={headingSizes[search.size]}>
224
+ {heading}
225
+ </div>
226
+ )}
227
+ {children}
228
+ </div>
229
+ );
220
230
  }
221
231
  export function SearchListItem({
222
- value,
223
- keywords = [],
224
- onSelect,
225
- className,
226
- ...props
232
+ value,
233
+ keywords = [],
234
+ onSelect,
235
+ className,
236
+ ...props
227
237
  }: Omit<ComponentProps<"button">, "value" | "onSelect"> & {
228
- value: string
229
- keywords?: string[]
230
- onSelect?: (value: string) => void
238
+ value: string;
239
+ keywords?: string[];
240
+ onSelect?: (value: string) => void;
231
241
  }) {
232
- const search = useSearch()
233
- const id = useId()
234
- const haystack = [value, ...keywords].join(" ").toLocaleLowerCase()
235
- const visible = search.query
236
- .toLocaleLowerCase()
237
- .trim()
238
- .split(/\s+/)
239
- .every((word) => haystack.includes(word))
240
- useEffect(() => {
241
- search.register(id, visible && !props.disabled)
242
- return () => search.register(id, null)
243
- }, [id, visible, props.disabled, search.register])
244
- return (
245
- <button
246
- {...props}
247
- type="button"
248
- id={id}
249
- role="option"
250
- tabIndex={-1}
251
- hidden={!visible}
252
- data-search-result=""
253
- aria-selected={search.active === id}
254
- data-selected={search.active === id ? "true" : undefined}
255
- className={cn(itemSizes[search.size], className)}
256
- onMouseDown={(event) => {
257
- props.onMouseDown?.(event)
258
- if (!event.defaultPrevented) event.preventDefault()
259
- }}
260
- onClick={(event) => {
261
- props.onClick?.(event)
262
- if (!event.defaultPrevented) onSelect?.(value)
263
- }}
264
- />
265
- )
242
+ const search = useSearch();
243
+ const id = useId();
244
+ const haystack = [value, ...keywords].join(" ").toLocaleLowerCase();
245
+ const visible = search.query
246
+ .toLocaleLowerCase()
247
+ .trim()
248
+ .split(/\s+/)
249
+ .every((word) => haystack.includes(word));
250
+ useEffect(() => {
251
+ search.register(id, visible && !props.disabled);
252
+ return () => search.register(id, null);
253
+ }, [id, visible, props.disabled, search.register]);
254
+ return (
255
+ <button
256
+ {...props}
257
+ type="button"
258
+ id={id}
259
+ role="option"
260
+ tabIndex={-1}
261
+ hidden={!visible}
262
+ data-search-result=""
263
+ aria-selected={search.active === id}
264
+ data-selected={search.active === id ? "true" : undefined}
265
+ className={cn(itemSizes[search.size], className)}
266
+ onMouseDown={(event) => {
267
+ props.onMouseDown?.(event);
268
+ if (!event.defaultPrevented) event.preventDefault();
269
+ }}
270
+ onClick={(event) => {
271
+ props.onClick?.(event);
272
+ if (!event.defaultPrevented) onSelect?.(value);
273
+ }}
274
+ />
275
+ );
266
276
  }
@@ -1,29 +1,29 @@
1
- import { useId, type HTMLAttributes, type ReactNode } from "react"
2
- import { cn } from "../../lib/class-names"
1
+ import { useId, type HTMLAttributes, type ReactNode } from "react";
2
+ import { cn } from "../../lib/class-names";
3
3
 
4
- export type SectionSize = "sm" | "md" | "lg"
4
+ export type SectionSize = "sm" | "md" | "lg";
5
5
 
6
6
  const titleSizes: Record<SectionSize, string> = {
7
- sm: "px-3 text-caption",
8
- md: "px-4 text-footnote",
9
- lg: "px-5 text-subheadline",
10
- }
7
+ sm: "px-3 text-caption",
8
+ md: "px-4 text-footnote",
9
+ lg: "px-5 text-subheadline",
10
+ };
11
11
 
12
12
  const footerSizes: Record<SectionSize, string> = {
13
- sm: "px-3",
14
- md: "px-4",
15
- lg: "px-5",
16
- }
13
+ sm: "px-3",
14
+ md: "px-4",
15
+ lg: "px-5",
16
+ };
17
17
 
18
18
  export interface SectionProps extends HTMLAttributes<HTMLElement> {
19
- /** Optional group heading and accessible name. */
20
- title?: string
21
- /** Supporting text below the grouped rows. */
22
- footer?: ReactNode
23
- /** Rows, typically LabeledContent. */
24
- children?: ReactNode
25
- /** Title and footer text scale + padding. @default "md" */
26
- size?: SectionSize
19
+ /** Optional group heading and accessible name. */
20
+ title?: string;
21
+ /** Supporting text below the grouped rows. */
22
+ footer?: ReactNode;
23
+ /** Rows, typically LabeledContent. */
24
+ children?: ReactNode;
25
+ /** Title and footer text scale + padding. @default "md" */
26
+ size?: SectionSize;
27
27
  }
28
28
 
29
29
  /**
@@ -32,44 +32,44 @@ export interface SectionProps extends HTMLAttributes<HTMLElement> {
32
32
  * usually `LabeledContent`; dividers between them come for free.
33
33
  */
34
34
  export function Section({
35
- title,
36
- footer,
37
- children,
38
- className,
39
- size = "md",
40
- ...props
35
+ title,
36
+ footer,
37
+ children,
38
+ className,
39
+ size = "md",
40
+ ...props
41
41
  }: SectionProps) {
42
- const headingId = useId()
43
- return (
44
- <section
45
- aria-labelledby={title ? headingId : undefined}
46
- className={cn("flex flex-col gap-2", className)}
47
- {...props}
48
- >
49
- {title && (
50
- <h2
51
- id={headingId}
52
- className={cn(titleSizes[size], "font-medium text-label-tertiary")}
53
- >
54
- {title}
55
- </h2>
56
- )}
57
- {/* A group can be only a note, e.g. where to find something on this device. */}
58
- {children ? (
59
- <div className="flex flex-col divide-y divide-separator/60 overflow-hidden rounded-3xl border border-separator/60 bg-elevated">
60
- {children}
61
- </div>
62
- ) : null}
63
- {footer && (
64
- <div
65
- className={cn(
66
- footerSizes[size],
67
- "text-caption leading-snug text-label-tertiary"
68
- )}
69
- >
70
- {footer}
71
- </div>
72
- )}
73
- </section>
74
- )
42
+ const headingId = useId();
43
+ return (
44
+ <section
45
+ aria-labelledby={title ? headingId : undefined}
46
+ className={cn("flex flex-col gap-2", className)}
47
+ {...props}
48
+ >
49
+ {title && (
50
+ <h2
51
+ id={headingId}
52
+ className={cn(titleSizes[size], "text-label-tertiary font-medium")}
53
+ >
54
+ {title}
55
+ </h2>
56
+ )}
57
+ {/* A group can be only a note, e.g. where to find something on this device. */}
58
+ {children ? (
59
+ <div className="divide-separator/60 border-separator/60 bg-elevated flex flex-col divide-y overflow-hidden rounded-3xl border">
60
+ {children}
61
+ </div>
62
+ ) : null}
63
+ {footer && (
64
+ <div
65
+ className={cn(
66
+ footerSizes[size],
67
+ "text-caption text-label-tertiary leading-snug",
68
+ )}
69
+ >
70
+ {footer}
71
+ </div>
72
+ )}
73
+ </section>
74
+ );
75
75
  }