@passui/react 0.1.45 → 0.1.47
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/README.md +57 -46
- package/dist/types/src/components/accordion/accordion.d.ts +1 -1
- package/dist/types/src/components/breadcrumb/breadcrumb.d.ts +1 -1
- package/dist/types/src/components/inline-disclosure/inline-disclosure.d.ts +1 -1
- package/dist/types/src/components/popover/anchored-surface.d.ts +5 -1
- package/dist/types/src/components/popover/index.d.ts +41 -0
- package/dist/types/src/components/popover/popover-content.d.ts +38 -0
- package/dist/types/src/components/popover/popover-root.d.ts +22 -0
- package/dist/types/src/components/popover/popover-trigger.d.ts +23 -0
- package/dist/types/src/components/stepper/rolling-digit.d.ts +1 -1
- package/dist/types/src/components/table/table.d.ts +1 -1
- package/dist/types/src/components/view-controller/view-controller.d.ts +2 -2
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/machines/popover.machine.d.ts +30 -0
- package/package.json +307 -307
- package/src/appearance/index.ts +2 -2
- package/src/appearance/platform.tsx +76 -60
- package/src/appearance/theme.tsx +409 -377
- package/src/components/accordion/accordion.tsx +58 -49
- package/src/components/ambient/ambient.tsx +25 -25
- package/src/components/ambient/index.ts +1 -1
- package/src/components/aspect-ratio/aspect-ratio.tsx +21 -16
- package/src/components/breadcrumb/breadcrumb.tsx +63 -57
- package/src/components/button/button-styles.ts +39 -32
- package/src/components/button/button.tsx +91 -84
- package/src/components/button/icon-button.tsx +27 -25
- package/src/components/button/pending-spinner.tsx +8 -8
- package/src/components/button/use-pending-indicator.ts +11 -11
- package/src/components/chip/chip.tsx +35 -30
- package/src/components/code-block/code-block.tsx +40 -39
- package/src/components/combo-box/combo-box.tsx +5 -5
- package/src/components/command/command.tsx +124 -94
- package/src/components/compact-date-picker/calendar-month.ts +75 -53
- package/src/components/compact-date-picker/compact-date-picker.tsx +308 -289
- package/src/components/compact-time-picker/compact-time-picker.tsx +288 -281
- package/src/components/compact-time-picker/time-value.ts +32 -29
- package/src/components/content-unavailable-view/content-unavailable-view.tsx +64 -48
- package/src/components/content-view/content-view.tsx +168 -159
- package/src/components/content-view/index.ts +1 -1
- package/src/components/context-menu/conceal-menu-source.ts +25 -25
- package/src/components/context-menu/context-menu-host.tsx +439 -402
- package/src/components/context-menu/context-menu-store.ts +125 -123
- package/src/components/context-menu/menu-navigation.ts +25 -18
- package/src/components/context-menu/menu-position.ts +33 -29
- package/src/components/context-menu/menu-reveal.ts +32 -32
- package/src/components/context-menu/use-context-menu.ts +63 -56
- package/src/components/conversation/conversation.tsx +265 -263
- package/src/components/conversation/index.ts +1 -1
- package/src/components/date-picker/date-picker.tsx +95 -94
- package/src/components/date-picker/date-value.ts +20 -10
- package/src/components/dialog/dialog-host.tsx +105 -103
- package/src/components/dialog/dialog-store.ts +37 -36
- package/src/components/dialog/dialog.tsx +192 -179
- package/src/components/dialog/index.ts +1 -1
- package/src/components/disclosure-group/disclosure-group.tsx +93 -86
- package/src/components/divider/divider.tsx +24 -22
- package/src/components/inline-disclosure/inline-disclosure.tsx +40 -36
- package/src/components/keyboard-shortcut/keyboard-shortcut.tsx +23 -19
- package/src/components/labeled-content/labeled-content.tsx +68 -68
- package/src/components/locale-switcher/index.ts +1 -1
- package/src/components/locale-switcher/locale-switcher.tsx +278 -251
- package/src/components/menu/menu.tsx +75 -58
- package/src/components/morph/index.ts +2 -2
- package/src/components/morph/morph.tsx +227 -201
- package/src/components/morph/use-morph-disclosure.ts +19 -19
- package/src/components/one-time-code/one-time-code.tsx +216 -200
- package/src/components/picker/picker-trigger.ts +16 -13
- package/src/components/picker/picker.tsx +371 -345
- package/src/components/popover/anchored-surface.tsx +253 -229
- package/src/components/popover/index.ts +50 -0
- package/src/components/popover/popover-content.tsx +374 -0
- package/src/components/popover/popover-root.tsx +97 -0
- package/src/components/popover/popover-trigger.tsx +71 -0
- package/src/components/profile-image/profile-image.tsx +80 -64
- package/src/components/progress-view/progress-view.tsx +61 -59
- package/src/components/progress-view/use-liquid-progress.ts +38 -38
- package/src/components/radio-group/radio-group.tsx +71 -68
- package/src/components/search-field/expanding-search-field.tsx +191 -177
- package/src/components/search-field/search-field.tsx +59 -50
- package/src/components/search-list/search-list.tsx +246 -236
- package/src/components/section/section.tsx +58 -58
- package/src/components/secure-field/secure-field.tsx +3 -3
- package/src/components/segmented-control/segmented-control.tsx +136 -136
- package/src/components/sheet/modal-environment.ts +71 -64
- package/src/components/sheet/sheet-portal.tsx +58 -53
- package/src/components/sheet/sheet.tsx +146 -141
- package/src/components/sheet/use-sheet-focus.ts +97 -82
- package/src/components/sidebar/sidebar.tsx +78 -78
- package/src/components/skeleton/skeleton.tsx +30 -21
- package/src/components/slider/slider.tsx +113 -104
- package/src/components/slosh-slider/liquid-step.ts +32 -31
- package/src/components/slosh-slider/slosh-slider.tsx +265 -228
- package/src/components/stat-tile/index.ts +1 -1
- package/src/components/stat-tile/stat-tile.tsx +79 -68
- package/src/components/status-indicator/status-indicator.tsx +40 -37
- package/src/components/stepper/rolling-digit.tsx +82 -68
- package/src/components/stepper/stepper.tsx +122 -113
- package/src/components/swatch-picker/swatch-picker.tsx +120 -120
- package/src/components/tab-bar/tab-bar.tsx +149 -148
- package/src/components/tab-view/tab-view.tsx +140 -137
- package/src/components/table/table.tsx +117 -98
- package/src/components/text-editor/text-editor.tsx +34 -29
- package/src/components/text-field/text-field.tsx +182 -180
- package/src/components/toast/toast-store.ts +127 -115
- package/src/components/toast/toaster.tsx +361 -347
- package/src/components/toggle/toggle.tsx +297 -282
- package/src/components/tooltip/tooltip-host.tsx +157 -150
- package/src/components/view-controller/index.ts +1 -1
- package/src/components/view-controller/view-controller.tsx +64 -53
- package/src/components/wake-slider/wake-slider.tsx +220 -205
- package/src/hooks/scroll/minimize-decision.ts +11 -7
- package/src/hooks/use-anchored-disclosure.ts +19 -19
- package/src/hooks/use-anchored-placement.ts +29 -25
- package/src/hooks/use-dismiss.ts +33 -29
- package/src/hooks/use-floating-dismiss.ts +24 -22
- package/src/hooks/use-media-query.ts +67 -60
- package/src/hooks/use-minimize-on-scroll.ts +69 -60
- package/src/hooks/use-visual-viewport.ts +63 -63
- package/src/hooks/viewport/types.ts +16 -16
- package/src/hooks/viewport/viewport-metrics.ts +16 -10
- package/src/icons/icons.tsx +440 -328
- package/src/index.ts +91 -91
- package/src/lib/anchored-placement.ts +70 -65
- package/src/lib/class-names.ts +14 -13
- package/src/lib/clipboard.ts +8 -7
- package/src/lib/date.ts +61 -60
- package/src/lib/floating-layers.ts +39 -24
- package/src/lib/haptics.ts +10 -10
- package/src/lib/language.ts +9 -9
- package/src/lib/range-value.ts +27 -21
- package/src/lib/screen-edges.ts +33 -30
- package/src/lib/shapes.ts +38 -34
- package/src/machines/popover.machine.ts +51 -0
- package/src/motion/transitions.ts +46 -43
- package/src/styles/code-block.css +69 -43
- package/src/styles/motion-controls.css +393 -191
- package/src/tokens.ts +1 -1
- package/dist/types/src/components/popover/popover-fit.d.ts +0 -21
- package/dist/types/src/components/popover/popover.d.ts +0 -21
- package/src/components/popover/popover-fit.ts +0 -69
- package/src/components/popover/popover.tsx +0 -334
|
@@ -1,266 +1,276 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
loop = true,
|
|
50
|
+
open = true,
|
|
51
|
+
size = "md",
|
|
52
|
+
onKeyDown,
|
|
53
|
+
className,
|
|
54
|
+
...props
|
|
55
55
|
}: ComponentProps<"div"> & {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
loop?: boolean;
|
|
57
|
+
open?: boolean;
|
|
58
|
+
/** Field, item and heading density. @default "md" */
|
|
59
|
+
size?: SearchListSize;
|
|
60
60
|
}) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
151
|
+
value,
|
|
152
|
+
onValueChange,
|
|
153
|
+
className,
|
|
154
|
+
...props
|
|
152
155
|
}: Omit<ComponentProps<"input">, "value" | "onChange"> & {
|
|
153
|
-
|
|
154
|
-
|
|
156
|
+
value?: string;
|
|
157
|
+
onValueChange?: (value: string) => void;
|
|
155
158
|
}) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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({
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
192
|
-
|
|
199
|
+
const search = useSearch();
|
|
200
|
+
return search.visible ? null : <div {...props} role="status" />;
|
|
193
201
|
}
|
|
194
202
|
export function SearchListGroup({
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
203
|
+
heading,
|
|
204
|
+
children,
|
|
205
|
+
className,
|
|
206
|
+
...props
|
|
199
207
|
}: ComponentProps<"div"> & {
|
|
200
|
-
|
|
208
|
+
heading?: React.ReactNode;
|
|
201
209
|
}) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
232
|
+
value,
|
|
233
|
+
keywords = [],
|
|
234
|
+
onSelect,
|
|
235
|
+
className,
|
|
236
|
+
...props
|
|
227
237
|
}: Omit<ComponentProps<"button">, "value" | "onSelect"> & {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
238
|
+
value: string;
|
|
239
|
+
keywords?: string[];
|
|
240
|
+
onSelect?: (value: string) => void;
|
|
231
241
|
}) {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
title,
|
|
36
|
+
footer,
|
|
37
|
+
children,
|
|
38
|
+
className,
|
|
39
|
+
size = "md",
|
|
40
|
+
...props
|
|
41
41
|
}: SectionProps) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
}
|