@stll/ui 0.59.0 → 0.60.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/README.md +120 -4
- package/dist/calendar/day-calendar.d.ts +14 -4
- package/dist/calendar/day-calendar.js +52 -20
- package/dist/calendar/day-calendar.logic.d.ts +3 -0
- package/dist/calendar/day-calendar.logic.js +4 -1
- package/dist/components/accordion.d.ts +17 -3
- package/dist/components/accordion.js +17 -3
- package/dist/components/button.d.ts +56 -5
- package/dist/components/button.js +97 -15
- package/dist/components/button.native-classes.d.ts +17 -0
- package/dist/components/button.native-classes.js +176 -0
- package/dist/components/button.native.d.ts +5 -2
- package/dist/components/button.native.js +17 -134
- package/dist/components/button.shared.d.ts +24 -2
- package/dist/components/button.shared.js +15 -1
- package/dist/components/combobox.d.ts +3 -1
- package/dist/components/combobox.js +3 -2
- package/dist/components/date-picker-popover.d.ts +2 -0
- package/dist/components/date-picker-popover.js +5 -3
- package/dist/components/details-grid.d.ts +21 -2
- package/dist/components/details-grid.js +75 -20
- package/dist/components/form.d.ts +9 -3
- package/dist/components/form.js +24 -3
- package/dist/components/input-control.d.ts +9 -0
- package/dist/components/input-control.js +10 -1
- package/dist/components/input.d.ts +7 -1
- package/dist/components/input.js +4 -4
- package/dist/components/label.d.ts +24 -2
- package/dist/components/label.js +21 -2
- package/dist/components/number-input.d.ts +9 -2
- package/dist/components/number-input.js +18 -6
- package/dist/components/textarea.d.ts +14 -1
- package/dist/components/textarea.js +11 -5
- package/dist/index.d.ts +10 -6
- package/dist/index.js +6 -3
- package/dist/kanban/index.d.ts +5 -2
- package/dist/kanban/index.js +4 -1
- package/dist/kanban/sortable-edge.d.ts +39 -0
- package/dist/kanban/sortable-edge.js +27 -0
- package/dist/kanban/sortable-interactions.d.ts +145 -0
- package/dist/kanban/sortable-interactions.js +503 -0
- package/dist/kanban/sortable-interactions.logic.d.ts +79 -0
- package/dist/kanban/sortable-interactions.logic.js +210 -0
- package/dist/kanban/subgroup-board.d.ts +27 -2
- package/dist/kanban/subgroup-board.js +26 -1
- package/dist/kanban/virtual-cell.d.ts +18 -1
- package/dist/kanban/virtual-cell.js +70 -17
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ cannot reach into product code by accident. Lint enforces that boundary; the
|
|
|
11
11
|
export map below is what "part of the design system" means.
|
|
12
12
|
|
|
13
13
|
Peer dependencies: `react`, `react-dom`, `@base-ui/react`, `tailwindcss` (v4),
|
|
14
|
-
and `@tanstack/react-virtual`.
|
|
14
|
+
`@dnd-kit/core`, `@dnd-kit/sortable`, and `@tanstack/react-virtual`.
|
|
15
15
|
|
|
16
16
|
Derived in part from [coss ui](https://github.com/cosscom/ui) (MIT); see
|
|
17
17
|
`THIRD_PARTY_NOTICES`.
|
|
@@ -104,11 +104,101 @@ unavailable state.
|
|
|
104
104
|
minor. They will be removed after that; the export guard checks that both
|
|
105
105
|
spellings land on the same module for as long as they both exist.
|
|
106
106
|
|
|
107
|
-
##
|
|
107
|
+
## Sortable boards
|
|
108
108
|
|
|
109
109
|
`@stll/ui/kanban` provides board matrices, subgroup swimlanes, bounded virtual
|
|
110
|
-
cells, and
|
|
111
|
-
persisted mutations.
|
|
110
|
+
cells, and input/accessibility primitives. The caller keeps domain identifiers,
|
|
111
|
+
card rendering, permissions, and persisted mutations. Wrap sortable boards in
|
|
112
|
+
`KanbanSortableBoard`, render items through `useKanbanSortable`, and attach the
|
|
113
|
+
returned bindings to `KanbanDragHandle` or `KanbanCardDragSurface`. This public
|
|
114
|
+
contract supports external consumers with keyboard navigation across virtual rows
|
|
115
|
+
and matrix cells, touch scrolling and long-press dragging, and host-controlled
|
|
116
|
+
collapsed-band peeking. It remains supported independently of which interaction
|
|
117
|
+
implementation the application board uses.
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
import {
|
|
121
|
+
KanbanDragHandle,
|
|
122
|
+
KanbanSortableBoard,
|
|
123
|
+
KanbanVirtualCell,
|
|
124
|
+
useKanbanSortable,
|
|
125
|
+
} from "@stll/ui/kanban";
|
|
126
|
+
|
|
127
|
+
const Card = ({ id }: { id: string }) => {
|
|
128
|
+
const { activator, setNodeRef, style } = useKanbanSortable({
|
|
129
|
+
activation: { type: "handle" },
|
|
130
|
+
id,
|
|
131
|
+
});
|
|
132
|
+
if (activator.type !== "handle") {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
return (
|
|
136
|
+
<article ref={setNodeRef} style={style}>
|
|
137
|
+
<KanbanDragHandle bindings={activator.bindings} label="Move card" />
|
|
138
|
+
</article>
|
|
139
|
+
);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
<KanbanSortableBoard onDragEnd={handleDragEnd}>
|
|
143
|
+
<KanbanVirtualCell
|
|
144
|
+
rows={cardIds}
|
|
145
|
+
getRowKey={(id) => id}
|
|
146
|
+
renderRow={(id) => <Card id={id} />}
|
|
147
|
+
pagination={{ type: "none" }}
|
|
148
|
+
sortable={{
|
|
149
|
+
dropTarget: { id: "column", position: { column: 0, lane: 0 } },
|
|
150
|
+
getRowId: (id) => id,
|
|
151
|
+
}}
|
|
152
|
+
/>
|
|
153
|
+
</KanbanSortableBoard>;
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
For collapsed subgroup bands, the host maps each registered drop target to its
|
|
157
|
+
band and clears the drag state on both drop and cancellation. Populate
|
|
158
|
+
`bandIdByDropTargetId` from the same cell/slot registrations that supply the
|
|
159
|
+
sortable target IDs (including folded-band slots).
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
const [dragState, setDragState] = useState<
|
|
163
|
+
{ type: "idle" } | { type: "dragging"; bandId: string | null }
|
|
164
|
+
>({ type: "idle" });
|
|
165
|
+
|
|
166
|
+
<KanbanSortableBoard
|
|
167
|
+
onDragStart={() => setDragState({ type: "dragging", bandId: null })}
|
|
168
|
+
onDragOver={({ over }) => {
|
|
169
|
+
setDragState({
|
|
170
|
+
type: "dragging",
|
|
171
|
+
bandId:
|
|
172
|
+
over === null ? null : (bandIdByDropTargetId.get(over.id) ?? null),
|
|
173
|
+
});
|
|
174
|
+
}}
|
|
175
|
+
onDragEnd={(event) => {
|
|
176
|
+
setDragState({ type: "idle" });
|
|
177
|
+
handleDragEnd(event);
|
|
178
|
+
}}
|
|
179
|
+
onDragCancel={() => setDragState({ type: "idle" })}
|
|
180
|
+
>
|
|
181
|
+
<KanbanSubgroupBoard
|
|
182
|
+
{...boardProps}
|
|
183
|
+
isDragging={dragState.type === "dragging"}
|
|
184
|
+
dragOverBandId={dragState.type === "dragging" ? dragState.bandId : null}
|
|
185
|
+
/>
|
|
186
|
+
</KanbanSortableBoard>;
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The default sensors are mouse (8px movement), touch (150ms delay with 8px
|
|
190
|
+
tolerance), and keyboard. Scroll containers retain `touch-action: auto`.
|
|
191
|
+
`KanbanDragHandle` and `{ type: "item" }` activation disable touch panning on
|
|
192
|
+
their activation surfaces. Use `{ type: "card" }` with
|
|
193
|
+
`KanbanCardDragSurface` to preserve scrolling from the card while supporting
|
|
194
|
+
long-press dragging. `KanbanSortableBoard` also accepts
|
|
195
|
+
custom `sensors`, `accessibility`, collision detection, keyboard coordinates,
|
|
196
|
+
auto-scroll options, and an `overlay` render function.
|
|
197
|
+
|
|
198
|
+
`getKanbanHorizontalEdge` takes `input: "pointer"` with a current client-x and
|
|
199
|
+
`direction: "ltr" | "rtl"` for mouse and touch input. Keyboard calls use
|
|
200
|
+
`input: "keyboard"` and require source and target indices, so every move has a
|
|
201
|
+
logical edge without relying on ambiguous geometry.
|
|
112
202
|
|
|
113
203
|
For Group/Sub-group boards, build one canonical matrix and render it with the
|
|
114
204
|
installable layout and virtual cell:
|
|
@@ -145,6 +235,32 @@ const matrix = buildKanbanBoardMatrix({
|
|
|
145
235
|
/>;
|
|
146
236
|
```
|
|
147
237
|
|
|
238
|
+
When rows inside a virtual cell are sortable, give the cell a unique drop target
|
|
239
|
+
and its ordered matrix position. The cell registers its actual scroll element,
|
|
240
|
+
including when `rows` is empty, and owns the matching `SortableContext`.
|
|
241
|
+
|
|
242
|
+
```tsx
|
|
243
|
+
<KanbanVirtualCell
|
|
244
|
+
getRowKey={(row) => row.id}
|
|
245
|
+
pagination={{ type: "none" }}
|
|
246
|
+
renderRow={(row) => <Card row={row} />}
|
|
247
|
+
rows={cell.rows}
|
|
248
|
+
sortable={{
|
|
249
|
+
dropTarget: {
|
|
250
|
+
id: cellId,
|
|
251
|
+
position: { column: columnIndex, lane: laneIndex },
|
|
252
|
+
},
|
|
253
|
+
getRowId: (row) => row.id,
|
|
254
|
+
}}
|
|
255
|
+
/>
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
The default keyboard coordinates move through items in their cell before
|
|
259
|
+
crossing into adjacent columns or subgroup lanes; empty cells remain reachable.
|
|
260
|
+
Pointer and touch drops outside registered board targets cancel. Choose an
|
|
261
|
+
explicit `{ type: "handle" }` activation for the 44px handle, or `{ type:
|
|
262
|
+
"item" }` when the whole item is the intended activation surface.
|
|
263
|
+
|
|
148
264
|
### Column bands
|
|
149
265
|
|
|
150
266
|
Columns whose options carry a `band` (`KanbanColumnBand`, usually a status
|
|
@@ -9,18 +9,28 @@ export type DayCalendarTone = {
|
|
|
9
9
|
} | {
|
|
10
10
|
type: "neutral";
|
|
11
11
|
};
|
|
12
|
-
/**
|
|
13
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Whether an entry is final, still provisional (dashed), or only proposed:
|
|
14
|
+
* a suggestion is not an entry yet, so it is drawn lighter, dashed, and is a
|
|
15
|
+
* button the caller acts on.
|
|
16
|
+
*/
|
|
17
|
+
export type DayCalendarEntryState = "draft" | "final" | "suggestion";
|
|
14
18
|
type DayCalendarBlockFields = DayCalendarRange & {
|
|
15
19
|
accessibleLabel: string;
|
|
16
|
-
|
|
20
|
+
caption?: ReactNode;
|
|
17
21
|
id: string;
|
|
18
22
|
title: ReactNode;
|
|
19
23
|
tone: DayCalendarTone;
|
|
24
|
+
/** Shown on hover, for example the apps a `mixed` block merged. */
|
|
25
|
+
tooltip?: ReactNode;
|
|
20
26
|
};
|
|
21
27
|
export type DayCalendarBlock = (DayCalendarBlockFields & {
|
|
22
28
|
kind: "entry";
|
|
23
|
-
state: DayCalendarEntryState
|
|
29
|
+
state: Exclude<DayCalendarEntryState, "suggestion">;
|
|
30
|
+
}) | (DayCalendarBlockFields & {
|
|
31
|
+
kind: "entry";
|
|
32
|
+
onActivate: () => void;
|
|
33
|
+
state: "suggestion";
|
|
24
34
|
}) | (DayCalendarBlockFields & {
|
|
25
35
|
kind: "mixed";
|
|
26
36
|
}) | (DayCalendarBlockFields & {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { cn } from "../lib/utils.js";
|
|
2
|
+
import { Tooltip, TooltipContent as TooltipPopup, TooltipTrigger } from "../components/tooltip.js";
|
|
2
3
|
import { DAY_CALENDAR_SLOT_HEIGHT_PX, boundsHeight, extendRange, getDayBounds, getHourTicks, isRepeatedWallTime, layoutOverlaps, moveRange, offsetToTime, rangeFromDrag, slotAt, timeToOffset } from "./day-calendar.logic.js";
|
|
3
4
|
import { resolveMatterColor } from "../lib/matter-colors.js";
|
|
4
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
6
|
import { useState } from "react";
|
|
6
7
|
import { panic } from "better-result";
|
|
7
8
|
import { Temporal } from "temporal-polyfill/full";
|
|
@@ -305,7 +306,7 @@ const ColumnBlocks = ({ blocks, grid }) => {
|
|
|
305
306
|
}, block.id);
|
|
306
307
|
});
|
|
307
308
|
};
|
|
308
|
-
/** Below this height a block puts its title and
|
|
309
|
+
/** Below this height a block puts its title and caption on one line. */
|
|
309
310
|
const TWO_LINE_BLOCK_MIN_PX = 40;
|
|
310
311
|
/** A brief block paints at least this tall, unless the grid ends sooner. */
|
|
311
312
|
const BLOCK_MIN_PX = 6;
|
|
@@ -316,7 +317,8 @@ const BLOCK_KIND_CLASSES = {
|
|
|
316
317
|
};
|
|
317
318
|
const ENTRY_STATE_CLASSES = {
|
|
318
319
|
draft: "border border-dashed border-current/40",
|
|
319
|
-
final: ""
|
|
320
|
+
final: "",
|
|
321
|
+
suggestion: "border border-dashed border-current/45 outline-none hover:ring-1 hover:ring-ring/50 focus-visible:ring-2 focus-visible:ring-ring"
|
|
320
322
|
};
|
|
321
323
|
const DayCalendarBlockView = ({ block, grid, lane, range }) => {
|
|
322
324
|
const top = timeToOffset(range.startMs, grid);
|
|
@@ -327,36 +329,66 @@ const DayCalendarBlockView = ({ block, grid, lane, range }) => {
|
|
|
327
329
|
insetBlockStart: top,
|
|
328
330
|
insetInlineStart: `${String(lane.lane * 100 / lane.laneCount)}%`
|
|
329
331
|
};
|
|
332
|
+
const className = cn("text-foreground flex h-full w-full min-w-0 overflow-hidden rounded-md px-2.5 text-start text-xs", blockSize < TWO_LINE_BLOCK_MIN_PX ? "flex-row items-center gap-2" : "flex-col gap-0.5 py-1", BLOCK_KIND_CLASSES[block.kind], block.kind === "entry" && ENTRY_STATE_CLASSES[block.state], block.tone.type === "matter" && "border-s-[3px]", block.tone.type === "neutral" && "bg-muted");
|
|
333
|
+
const content = blockSize < 16 ? null : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
334
|
+
className: "min-w-0 shrink-0 truncate font-medium",
|
|
335
|
+
children: block.title
|
|
336
|
+
}), block.caption === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
337
|
+
className: "text-muted-foreground min-w-0 truncate tabular-nums",
|
|
338
|
+
children: block.caption
|
|
339
|
+
})] });
|
|
340
|
+
const shared = {
|
|
341
|
+
"aria-label": block.accessibleLabel,
|
|
342
|
+
className,
|
|
343
|
+
"data-block-id": block.id,
|
|
344
|
+
"data-kind": block.kind,
|
|
345
|
+
"data-slot": "day-calendar-block",
|
|
346
|
+
style: blockSurfaceStyle(block)
|
|
347
|
+
};
|
|
348
|
+
const surface = block.kind === "entry" && block.state === "suggestion" ? /* @__PURE__ */ jsx("button", {
|
|
349
|
+
...shared,
|
|
350
|
+
"data-state": block.state,
|
|
351
|
+
onClick: block.onActivate,
|
|
352
|
+
onKeyDown: (event) => event.stopPropagation(),
|
|
353
|
+
onPointerDown: (event) => event.stopPropagation(),
|
|
354
|
+
type: "button",
|
|
355
|
+
children: content
|
|
356
|
+
}) : /* @__PURE__ */ jsx("article", {
|
|
357
|
+
...shared,
|
|
358
|
+
"data-state": block.kind === "entry" ? block.state : void 0,
|
|
359
|
+
children: content
|
|
360
|
+
});
|
|
330
361
|
return /* @__PURE__ */ jsx("div", {
|
|
331
|
-
className: "
|
|
362
|
+
className: cn("absolute px-1.5 py-px", block.tooltip === void 0 && !(block.kind === "entry" && block.state === "suggestion") && "pointer-events-none"),
|
|
332
363
|
style: placement,
|
|
333
|
-
children: /* @__PURE__ */ jsxs(
|
|
334
|
-
"
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
"data-slot": "day-calendar-block",
|
|
338
|
-
style: blockSurfaceStyle(block),
|
|
339
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
340
|
-
className: "min-w-0 shrink-0 truncate font-medium",
|
|
341
|
-
children: block.title
|
|
342
|
-
}), block.detail === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
343
|
-
className: "text-muted-foreground min-w-0 truncate tabular-nums",
|
|
344
|
-
children: block.detail
|
|
345
|
-
})]
|
|
346
|
-
})
|
|
364
|
+
children: block.tooltip === void 0 ? surface : /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, { render: surface }), /* @__PURE__ */ jsx(TooltipPopup, {
|
|
365
|
+
className: "max-w-72",
|
|
366
|
+
children: block.tooltip
|
|
367
|
+
})] })
|
|
347
368
|
});
|
|
348
369
|
};
|
|
349
370
|
const MIXED_STRIPES = "repeating-linear-gradient(135deg, var(--muted) 0 6px, transparent 6px 12px)";
|
|
371
|
+
/** A suggestion's tint is half an entry's, so proposed time reads lighter. */
|
|
372
|
+
const TONE_TINT_PERCENT = {
|
|
373
|
+
entry: 16,
|
|
374
|
+
suggestion: 8
|
|
375
|
+
};
|
|
350
376
|
function blockSurfaceStyle(block) {
|
|
351
377
|
const tone = block.tone;
|
|
352
378
|
const stripes = block.kind === "mixed" ? MIXED_STRIPES : void 0;
|
|
379
|
+
const suggestion = block.kind === "entry" && block.state === "suggestion";
|
|
353
380
|
switch (tone.type) {
|
|
354
|
-
case "neutral": return {
|
|
381
|
+
case "neutral": return {
|
|
382
|
+
backgroundColor: suggestion ? "transparent" : void 0,
|
|
383
|
+
backgroundImage: stripes
|
|
384
|
+
};
|
|
355
385
|
case "matter": {
|
|
356
386
|
const color = resolveMatterColor(tone.matterId, tone.color);
|
|
387
|
+
const tint = suggestion ? TONE_TINT_PERCENT.suggestion : TONE_TINT_PERCENT.entry;
|
|
357
388
|
return {
|
|
358
|
-
backgroundColor: `color-mix(in srgb, ${color}
|
|
389
|
+
backgroundColor: `color-mix(in srgb, ${color} ${String(tint)}%, var(--card))`,
|
|
359
390
|
backgroundImage: stripes,
|
|
391
|
+
borderColor: suggestion ? color : void 0,
|
|
360
392
|
borderInlineStartColor: color
|
|
361
393
|
};
|
|
362
394
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
//#region src/calendar/day-calendar.logic.d.ts
|
|
2
2
|
export declare const DAY_CALENDAR_GRANULARITIES: readonly [15, 30, 60];
|
|
3
3
|
export type DayCalendarGranularity = (typeof DAY_CALENDAR_GRANULARITIES)[number];
|
|
4
|
+
/** Below this height a block cannot hold one line of its label, so it draws
|
|
5
|
+
* its surface only; its accessible label and tooltip still carry it. */
|
|
6
|
+
export declare const DAY_CALENDAR_ONE_LINE_BLOCK_MIN_PX = 16;
|
|
4
7
|
/**
|
|
5
8
|
* Height of one grid slot. Coarser grids get taller slots, but not
|
|
6
9
|
* proportionally: an hour stays tall enough for a two-line block.
|
|
@@ -6,6 +6,9 @@ const DAY_CALENDAR_GRANULARITIES = [
|
|
|
6
6
|
30,
|
|
7
7
|
60
|
|
8
8
|
];
|
|
9
|
+
/** Below this height a block cannot hold one line of its label, so it draws
|
|
10
|
+
* its surface only; its accessible label and tooltip still carry it. */
|
|
11
|
+
const DAY_CALENDAR_ONE_LINE_BLOCK_MIN_PX = 16;
|
|
9
12
|
/**
|
|
10
13
|
* Height of one grid slot. Coarser grids get taller slots, but not
|
|
11
14
|
* proportionally: an hour stays tall enough for a two-line block.
|
|
@@ -232,4 +235,4 @@ function compareIds(left, right) {
|
|
|
232
235
|
return left > right ? 1 : 0;
|
|
233
236
|
}
|
|
234
237
|
//#endregion
|
|
235
|
-
export { DAY_CALENDAR_GRANULARITIES, DAY_CALENDAR_SLOT_HEIGHT_PX, boundsHeight, clampToBounds, extendRange, getDayBounds, getHourTicks, granularityMs, isRepeatedWallTime, layoutOverlaps, mergeShortSegments, moveRange, offsetToTime, rangeFromDrag, slotAt, snapToGrid, timeToOffset };
|
|
238
|
+
export { DAY_CALENDAR_GRANULARITIES, DAY_CALENDAR_ONE_LINE_BLOCK_MIN_PX, DAY_CALENDAR_SLOT_HEIGHT_PX, boundsHeight, clampToBounds, extendRange, getDayBounds, getHourTicks, granularityMs, isRepeatedWallTime, layoutOverlaps, mergeShortSegments, moveRange, offsetToTime, rangeFromDrag, slotAt, snapToGrid, timeToOffset };
|
|
@@ -2,11 +2,25 @@ import { Accordion as Accordion$1 } from "@base-ui/react/accordion";
|
|
|
2
2
|
//#region src/components/accordion.d.ts
|
|
3
3
|
declare const Accordion: <Value = unknown>(props: Accordion$1.Root.Props<Value>) => import("react").JSX.Element;
|
|
4
4
|
declare const AccordionItem: ({ className, ...props }: Accordion$1.Item.Props) => import("react").JSX.Element;
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* - `default`: a section heading of a page or panel.
|
|
7
|
+
* - `sm`: body-size type at a 44px row, for a disclosure inside a dialog or
|
|
8
|
+
* card ("Show all permissions", advanced options).
|
|
9
|
+
* - `compact`: small type in a dense list.
|
|
10
|
+
*/
|
|
11
|
+
type AccordionSize = "default" | "sm" | "compact";
|
|
12
|
+
/**
|
|
13
|
+
* - `default`: the trigger reads as a heading.
|
|
14
|
+
* - `muted`: a quiet "show more" line in muted body weight that brightens on
|
|
15
|
+
* hover, for a disclosure that is secondary to what is around it.
|
|
16
|
+
*/
|
|
17
|
+
type AccordionTriggerVariant = "default" | "muted";
|
|
18
|
+
declare const AccordionTrigger: ({ className, children, size, variant, ...props }: Accordion$1.Trigger.Props & {
|
|
19
|
+
size?: AccordionSize;
|
|
20
|
+
variant?: AccordionTriggerVariant;
|
|
7
21
|
}) => import("react").JSX.Element;
|
|
8
22
|
declare const AccordionPanel: ({ className, children, size, ...props }: Accordion$1.Panel.Props & {
|
|
9
|
-
size?:
|
|
23
|
+
size?: AccordionSize;
|
|
10
24
|
}) => import("react").JSX.Element;
|
|
11
25
|
//#endregion
|
|
12
26
|
export { Accordion, AccordionPanel as AccordionContent, AccordionPanel, AccordionItem, AccordionTrigger };
|
|
@@ -13,10 +13,24 @@ const AccordionItem = ({ className, ...props }) => /* @__PURE__ */ jsx(Accordion
|
|
|
13
13
|
"data-slot": "accordion-item",
|
|
14
14
|
...props
|
|
15
15
|
});
|
|
16
|
-
const
|
|
16
|
+
const TRIGGER_SIZE_CLASS_NAME = {
|
|
17
|
+
compact: "py-2 text-xs",
|
|
18
|
+
default: "py-4 text-sm",
|
|
19
|
+
sm: "min-h-11 items-center py-2 text-sm"
|
|
20
|
+
};
|
|
21
|
+
const PANEL_SIZE_CLASS_NAME = {
|
|
22
|
+
compact: "pb-3",
|
|
23
|
+
default: "pb-4",
|
|
24
|
+
sm: "pb-2"
|
|
25
|
+
};
|
|
26
|
+
const TRIGGER_VARIANT_CLASS_NAME = {
|
|
27
|
+
default: "font-medium",
|
|
28
|
+
muted: "text-muted-foreground hover:text-foreground font-normal"
|
|
29
|
+
};
|
|
30
|
+
const AccordionTrigger = ({ className, children, size = "default", variant = "default", ...props }) => /* @__PURE__ */ jsx(Accordion$1.Header, {
|
|
17
31
|
className: "flex",
|
|
18
32
|
children: /* @__PURE__ */ jsxs(Accordion$1.Trigger, {
|
|
19
|
-
className: cn("focus-visible:ring-ring flex flex-1 cursor-pointer items-start justify-between gap-4 rounded-md text-start
|
|
33
|
+
className: cn("focus-visible:ring-ring flex flex-1 cursor-pointer items-start justify-between gap-4 rounded-md text-start outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-64 data-panel-open:*:data-[slot=accordion-indicator]:rotate-180", TRIGGER_SIZE_CLASS_NAME[size], TRIGGER_VARIANT_CLASS_NAME[variant], className),
|
|
20
34
|
"data-slot": "accordion-trigger",
|
|
21
35
|
...props,
|
|
22
36
|
children: [children, /* @__PURE__ */ jsx(ChevronDownIcon, {
|
|
@@ -30,7 +44,7 @@ const AccordionPanel = ({ className, children, size = "default", ...props }) =>
|
|
|
30
44
|
"data-slot": "accordion-panel",
|
|
31
45
|
...props,
|
|
32
46
|
children: /* @__PURE__ */ jsx("div", {
|
|
33
|
-
className: cn("pt-0", size
|
|
47
|
+
className: cn("pt-0", PANEL_SIZE_CLASS_NAME[size], className),
|
|
34
48
|
children
|
|
35
49
|
})
|
|
36
50
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ButtonContract } from "./button.shared.js";
|
|
1
|
+
import { ButtonContract, ButtonSize, ButtonVariant } from "./button.shared.js";
|
|
2
2
|
import { ReactNode } from "react";
|
|
3
3
|
import { useRender } from "@base-ui/react/use-render";
|
|
4
4
|
//#region src/components/button.d.ts
|
|
@@ -12,10 +12,31 @@ import { useRender } from "@base-ui/react/use-render";
|
|
|
12
12
|
* swaps the cursor so the control still reads as unavailable on hover.
|
|
13
13
|
*/
|
|
14
14
|
declare const buttonAccessibleDisabledClass = "cursor-not-allowed opacity-64";
|
|
15
|
+
declare const BUTTON_LAYOUT_CLASS_NAMES: {
|
|
16
|
+
readonly wrap: "h-auto min-h-11 items-start justify-start py-2 text-start whitespace-normal sm:h-auto";
|
|
17
|
+
};
|
|
15
18
|
declare const buttonVariants: (props?: ({
|
|
16
|
-
size?: "chip" | "default" | "icon" | "icon-lg" | "icon-sm" | "icon-xl" | "icon-xs" | "lg" | "row" | "sm" | "xl" | "xs" | null | undefined;
|
|
17
|
-
|
|
19
|
+
size?: "chip" | "default" | "icon" | "icon-2xs" | "icon-lg" | "icon-sm" | "icon-xl" | "icon-xs" | "inline" | "lg" | "row" | "sm" | "xl" | "xs" | null | undefined;
|
|
20
|
+
layout?: "wrap" | null | undefined;
|
|
21
|
+
variant?: "default" | "destructive" | "destructive-ghost" | "destructive-outline" | "ghost" | "link" | "muted" | "muted-destructive" | "outline" | "secondary" | null | undefined;
|
|
18
22
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
23
|
+
/**
|
|
24
|
+
* The outline a plain button's focus ring follows, matching its content:
|
|
25
|
+
* `box` for a card, row or block of text (the container radius), `round` for
|
|
26
|
+
* an avatar, a dot or a pill.
|
|
27
|
+
*/
|
|
28
|
+
declare const BUTTON_PLAIN_SHAPE_CLASS: {
|
|
29
|
+
readonly box: "rounded-lg";
|
|
30
|
+
readonly round: "rounded-full";
|
|
31
|
+
};
|
|
32
|
+
type ButtonPlainShape = keyof typeof BUTTON_PLAIN_SHAPE_CLASS;
|
|
33
|
+
/**
|
|
34
|
+
* Where the focus ring is drawn. `outside` (the default) rings the box with a
|
|
35
|
+
* gap; `inside` draws it within the box, for a target that fills a clipping
|
|
36
|
+
* container (an `overflow-hidden` card, a table cell, a thumbnail grid) where
|
|
37
|
+
* an outside ring would be cut off.
|
|
38
|
+
*/
|
|
39
|
+
type ButtonFocusRing = "outside" | "inside";
|
|
19
40
|
/** What a button's box holds: a glyph alone, or a visible label. */
|
|
20
41
|
declare const BUTTON_CONTENT: {
|
|
21
42
|
readonly icon: "icon";
|
|
@@ -39,17 +60,47 @@ declare const BUTTON_SIZE_CONTENT: {
|
|
|
39
60
|
readonly "icon-lg": "icon";
|
|
40
61
|
readonly "icon-sm": "icon";
|
|
41
62
|
readonly "icon-xl": "icon";
|
|
63
|
+
readonly "icon-2xs": "icon";
|
|
42
64
|
readonly "icon-xs": "icon";
|
|
65
|
+
readonly inline: "text";
|
|
43
66
|
readonly lg: "text";
|
|
44
67
|
readonly row: "text";
|
|
45
68
|
readonly sm: "text";
|
|
46
69
|
readonly xl: "text";
|
|
47
70
|
readonly xs: "text";
|
|
48
71
|
};
|
|
72
|
+
/**
|
|
73
|
+
* `selected` (./button.shared.ts) sets `data-pressed`, which every variant
|
|
74
|
+
* paints. The ARIA state (`aria-pressed`, `aria-checked`, `aria-current` or
|
|
75
|
+
* `aria-selected`) stays the caller's. For a set of mutually exclusive
|
|
76
|
+
* options prefer `ToggleGroup`, which owns that state.
|
|
77
|
+
*/
|
|
49
78
|
type ButtonProps = Omit<ButtonContract, "children" | "disabled"> & {
|
|
79
|
+
layout?: keyof typeof BUTTON_LAYOUT_CLASS_NAMES;
|
|
50
80
|
tooltip?: ReactNode;
|
|
81
|
+
focusRing?: ButtonFocusRing | undefined;
|
|
82
|
+
/** `variant="plain"` only: the outline its focus ring follows. */
|
|
83
|
+
shape?: ButtonPlainShape | undefined;
|
|
51
84
|
"data-accessible-disabled"?: "";
|
|
52
85
|
} & useRender.ComponentProps<"button">;
|
|
53
|
-
declare function Button({ className, variant, size, render, loading, icon, children, disabled, tooltip, "data-accessible-disabled": accessibleDisabled, ...props }: ButtonProps): import("react").JSX.Element;
|
|
86
|
+
declare function Button({ className, variant: variantProp, size, render, layout, loading, icon, children, disabled, tooltip, selected, focusRing, shape, "data-accessible-disabled": accessibleDisabled, ...props }: ButtonProps): import("react").JSX.Element;
|
|
87
|
+
/**
|
|
88
|
+
* A run with no box can anchor no tooltip or popup, and carries no variant,
|
|
89
|
+
* size or icon: its text is its look.
|
|
90
|
+
*/
|
|
91
|
+
type ButtonTextRunProps = Omit<ButtonProps, "children" | "className" | "focusRing" | "icon" | "loading" | "render" | "selected" | "shape" | "size" | "tooltip" | "variant"> & {
|
|
92
|
+
children: ReactNode;
|
|
93
|
+
className?: string | undefined;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* A long run of prose that acts as one button: a citation or a
|
|
97
|
+
* cross-reference in reader text. A <button> is an atomic box even at
|
|
98
|
+
* `display: inline`, so its label cannot break across the paragraph's lines;
|
|
99
|
+
* here the text sits in a native <label>, an inline box that wraps and copies
|
|
100
|
+
* with the prose, and the label forwards a press anywhere on the run to the
|
|
101
|
+
* <button> it holds, which takes its accessible name from the label. Use
|
|
102
|
+
* `Button size="inline"` for a short action.
|
|
103
|
+
*/
|
|
104
|
+
declare const ButtonTextRun: ({ children, className, disabled, ...props }: ButtonTextRunProps) => import("react").JSX.Element;
|
|
54
105
|
//#endregion
|
|
55
|
-
export { BUTTON_CONTENT, BUTTON_SIZE_CONTENT, Button, buttonAccessibleDisabledClass, buttonVariants };
|
|
106
|
+
export { BUTTON_CONTENT, BUTTON_SIZE_CONTENT, Button, type ButtonFocusRing, type ButtonPlainShape, type ButtonProps, type ButtonSize, ButtonTextRun, type ButtonVariant, buttonAccessibleDisabledClass, buttonVariants };
|