@charcuterie/ui 2.2.0 → 2.4.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/dist/Combobox/Combobox.d.ts +22 -4
- package/dist/Combobox/Combobox.d.ts.map +1 -1
- package/dist/Combobox/Combobox.js +142 -24
- package/dist/Combobox/Combobox.js.map +1 -1
- package/dist/Combobox/ComboboxOption.d.ts.map +1 -1
- package/dist/Combobox/ComboboxOption.js +29 -2
- package/dist/Combobox/ComboboxOption.js.map +1 -1
- package/dist/EmptyState/EmptyState.d.ts +6 -1
- package/dist/EmptyState/EmptyState.d.ts.map +1 -1
- package/dist/EmptyState/EmptyState.js.map +1 -1
- package/dist/Listbox/ListboxOption.d.ts.map +1 -1
- package/dist/Listbox/ListboxOption.js +22 -2
- package/dist/Listbox/ListboxOption.js.map +1 -1
- package/dist/Overlay/useAnchoredOverlay.d.ts +17 -5
- package/dist/Overlay/useAnchoredOverlay.d.ts.map +1 -1
- package/dist/Overlay/useAnchoredOverlay.js +23 -3
- package/dist/Overlay/useAnchoredOverlay.js.map +1 -1
- package/dist/Select/Select.d.ts.map +1 -1
- package/dist/Select/Select.js +8 -2
- package/dist/Select/Select.js.map +1 -1
- package/dist/controlStyles.d.ts +8 -0
- package/dist/controlStyles.d.ts.map +1 -1
- package/dist/controlStyles.js +8 -0
- package/dist/controlStyles.js.map +1 -1
- package/package.json +2 -2
- package/src/Combobox/Combobox.mdx +30 -0
- package/src/Combobox/Combobox.stories.tsx +150 -5
- package/src/Combobox/Combobox.test.tsx +120 -1
- package/src/Combobox/Combobox.tsx +273 -72
- package/src/Combobox/ComboboxOption.tsx +30 -5
- package/src/EmptyState/EmptyState.stories.tsx +15 -0
- package/src/EmptyState/EmptyState.test.tsx +16 -1
- package/src/EmptyState/EmptyState.tsx +6 -1
- package/src/Listbox/ListboxOption.tsx +22 -4
- package/src/Overlay/useAnchoredOverlay.ts +49 -8
- package/src/Select/Select.tsx +6 -5
- package/src/controlStyles.ts +8 -0
|
@@ -14,7 +14,8 @@ import {
|
|
|
14
14
|
useInteractions,
|
|
15
15
|
useRole,
|
|
16
16
|
} from "@floating-ui/react"
|
|
17
|
-
import type { ReactElement } from "react"
|
|
17
|
+
import type { ReactElement, RefObject } from "react"
|
|
18
|
+
import { createElement, useEffect } from "react"
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* The floating-ui block every anchored overlay — `Popover`, `Menu`,
|
|
@@ -67,6 +68,15 @@ export type AnchoredOverlayRole =
|
|
|
67
68
|
| "menu"
|
|
68
69
|
|
|
69
70
|
export type UseAnchoredOverlayOptions = {
|
|
71
|
+
/**
|
|
72
|
+
* Anchor the panel to a **pre-existing** element the consumer already
|
|
73
|
+
* rendered, instead of cloning a `trigger`. For the attached-input
|
|
74
|
+
* `Combobox`, whose reference is the consumer's own `<input>` (both the
|
|
75
|
+
* value and the query) — there is nothing to clone. Supply exactly one
|
|
76
|
+
* of `anchorRef` / `trigger`; with `anchorRef`, `clonedTrigger` is
|
|
77
|
+
* `null`.
|
|
78
|
+
*/
|
|
79
|
+
anchorRef?: RefObject<HTMLElement | null>
|
|
70
80
|
/**
|
|
71
81
|
* Both default **on** — Escape and outside-press are what makes an
|
|
72
82
|
* overlay an overlay. Switch one off for the honest exception (a
|
|
@@ -86,11 +96,21 @@ export type UseAnchoredOverlayOptions = {
|
|
|
86
96
|
onDismiss: () => void
|
|
87
97
|
placement?: Placement
|
|
88
98
|
role: AnchoredOverlayRole
|
|
89
|
-
/**
|
|
90
|
-
|
|
99
|
+
/**
|
|
100
|
+
* The control the panel hangs off. **Cloned, not wrapped.** Omit only
|
|
101
|
+
* when `anchorRef` is supplied instead.
|
|
102
|
+
*/
|
|
103
|
+
trigger?: ReactElement
|
|
91
104
|
}
|
|
92
105
|
|
|
106
|
+
// `useClonedChild` insists on exactly one element, and hooks cannot be
|
|
107
|
+
// called conditionally — so in `anchorRef` mode we still call it, on this
|
|
108
|
+
// throwaway, and discard the result. It is never rendered and never
|
|
109
|
+
// receives the floating reference (that goes to `anchorRef`).
|
|
110
|
+
const PLACEHOLDER_TRIGGER = createElement("span")
|
|
111
|
+
|
|
93
112
|
export const useAnchoredOverlay = ({
|
|
113
|
+
anchorRef,
|
|
94
114
|
isEscapeDismissable = true,
|
|
95
115
|
isOutsidePressDismissable = true,
|
|
96
116
|
isTriggerWidthMatched = false,
|
|
@@ -128,6 +148,8 @@ export const useAnchoredOverlay = ({
|
|
|
128
148
|
)
|
|
129
149
|
}
|
|
130
150
|
|
|
151
|
+
const isAnchored = anchorRef !== undefined
|
|
152
|
+
|
|
131
153
|
const { context, floatingStyles, refs } = useFloating({
|
|
132
154
|
// Read-only. Charcuterie remains the sole owner; floating-ui is
|
|
133
155
|
// told what is true and never decides it.
|
|
@@ -180,11 +202,30 @@ export const useAnchoredOverlay = ({
|
|
|
180
202
|
(referenceProps.id as string | undefined) ??
|
|
181
203
|
generatedTriggerId
|
|
182
204
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
205
|
+
// In anchor mode floating-ui's reference is the consumer's element, set
|
|
206
|
+
// imperatively — there is no cloned trigger to carry `refs.setReference`.
|
|
207
|
+
// `setReference` is stable; re-running on `isVisible` re-applies it when
|
|
208
|
+
// the panel opens (and after the anchor element mounts).
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
if (!isAnchored) {
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
refs.setReference(anchorRef.current)
|
|
215
|
+
}, [isAnchored, anchorRef, refs])
|
|
216
|
+
|
|
217
|
+
const clonedChild = useClonedChild(
|
|
218
|
+
trigger ?? PLACEHOLDER_TRIGGER,
|
|
219
|
+
{
|
|
220
|
+
...referenceProps,
|
|
221
|
+
id: triggerId,
|
|
222
|
+
// The anchor element already exists; the clone (a discarded
|
|
223
|
+
// placeholder here) must not also claim the floating reference.
|
|
224
|
+
...(isAnchored ? {} : { ref: refs.setReference }),
|
|
225
|
+
},
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
const clonedTrigger = isAnchored ? null : clonedChild
|
|
188
229
|
|
|
189
230
|
return {
|
|
190
231
|
clonedTrigger,
|
package/src/Select/Select.tsx
CHANGED
|
@@ -5,10 +5,7 @@ import type {
|
|
|
5
5
|
ReactNode,
|
|
6
6
|
} from "react"
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
CONTROL_SIZE_CLASS,
|
|
10
|
-
MIN_TOUCH_TARGET_CLASS,
|
|
11
|
-
} from "../controlStyles.ts"
|
|
8
|
+
import { CONTROL_SIZE_CLASS } from "../controlStyles.ts"
|
|
12
9
|
import {
|
|
13
10
|
DISABLED_CLASS,
|
|
14
11
|
FOCUS_RING_CLASS,
|
|
@@ -139,8 +136,12 @@ export const Select = ({
|
|
|
139
136
|
// removes, and forgetting to put one back leaves a control
|
|
140
137
|
// that looks exactly like a text input.
|
|
141
138
|
"pe-9",
|
|
139
|
+
// Height comes from the shared control-size/density system ONLY —
|
|
140
|
+
// no per-component touch-target floor. Touch is the density axis's
|
|
141
|
+
// job (`[data-density]` scales `--control-height-*` past 44px on a
|
|
142
|
+
// kiosk), so forcing 44px here made Select taller than Button et al.
|
|
143
|
+
// at desktop density. See the controls-share-one-height decision.
|
|
142
144
|
CONTROL_SIZE_CLASS[size],
|
|
143
|
-
MIN_TOUCH_TARGET_CLASS,
|
|
144
145
|
"hover:border-border-strong",
|
|
145
146
|
FOCUS_RING_CLASS,
|
|
146
147
|
DISABLED_CLASS,
|
package/src/controlStyles.ts
CHANGED
|
@@ -46,6 +46,14 @@ export const ICON_CONTROL_SIZE_CLASS: Record<
|
|
|
46
46
|
* density — a finger does not get smaller because the list is
|
|
47
47
|
* dense — so this is opt-in per control rather than baked into the
|
|
48
48
|
* size classes, which would make `compact` a lie.
|
|
49
|
+
*
|
|
50
|
+
* **Do NOT add this to a control that shares a form row** (`Button`,
|
|
51
|
+
* `Select`, a `Listbox`/`Combobox` trigger, `SegmentedControl`, …). It
|
|
52
|
+
* overrides the shared `CONTROL_SIZE_CLASS` height and makes that one
|
|
53
|
+
* control taller than its neighbours at desktop density — touch sizing is
|
|
54
|
+
* the density axis's job. Reserve this for a genuinely standalone tap
|
|
55
|
+
* target. See
|
|
56
|
+
* `docs/decisions/2026-08-05-controls-share-one-height-no-per-component-touch-floor.md`.
|
|
49
57
|
*/
|
|
50
58
|
export const MIN_TOUCH_TARGET_CLASS =
|
|
51
59
|
"min-h-(--control-min-touch-target) min-w-(--control-min-touch-target)"
|