@dorsk/tsumikit 0.67.0 → 0.68.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 +104 -7
- package/dist/components/atoms/Button.svelte +118 -10
- package/dist/components/atoms/Button.svelte.d.ts +4 -0
- package/dist/components/layouts/NavBar.svelte +184 -30
- package/dist/components/layouts/NavBar.svelte.d.ts +19 -3
- package/dist/components/molecules/FileButton.svelte +2 -2
- package/dist/components/molecules/FileButton.svelte.d.ts +1 -1
- package/dist/components/molecules/GlyphStack.svelte +12 -19
- package/dist/components/molecules/IconButton.svelte +1 -1
- package/dist/components/molecules/Popover.svelte +5 -6
- package/dist/components/molecules/Popover.svelte.d.ts +1 -1
- package/dist/components/molecules/RadialMenu.svelte +579 -0
- package/dist/components/molecules/RadialMenu.svelte.d.ts +42 -0
- package/dist/components/molecules/SortableList.svelte +720 -0
- package/dist/components/molecules/SortableList.svelte.d.ts +116 -0
- package/dist/components/molecules/sortable-list-order.d.ts +68 -0
- package/dist/components/molecules/sortable-list-order.js +133 -0
- package/dist/floating.d.ts +38 -0
- package/dist/floating.js +38 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/slide-select.d.ts +26 -0
- package/dist/slide-select.js +53 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -207,7 +207,7 @@ sanctioned escape hatch for everything else.
|
|
|
207
207
|
| `Divider` | `--divider-color`, `--divider-spacing` |
|
|
208
208
|
| `Gauge` | `--gauge-w`, `--gauge-h`, `--gauge-fill` |
|
|
209
209
|
| `MasterDetail` | `--md-list-w`, `--md-gap`, `--md-divider` |
|
|
210
|
-
| `NavBar` | `--navbar-max` |
|
|
210
|
+
| `NavBar` | `--navbar-max`, `--navbar-center-w`, `--navbar-center-raise` |
|
|
211
211
|
| `Callout` | `--callout-tone` |
|
|
212
212
|
| `Fieldset` | `--fieldset-pad`, `--fieldset-border` |
|
|
213
213
|
| `GitRef` | `--git-ref-tone`, `--git-ref-max-width` |
|
|
@@ -215,6 +215,8 @@ sanctioned escape hatch for everything else.
|
|
|
215
215
|
| `Combobox` | `--cb-bg`, `--cb-border`, `--cb-radius`, `--cb-active-bg`, `--cb-min-width`, `--cb-max-height` |
|
|
216
216
|
| `Toggle` | `--toggle-accent`, `--toggle-size` |
|
|
217
217
|
| `Popover` | `--pop-trigger-bg`, `--pop-trigger-fg`, `--pop-trigger-border`, `--pop-trigger-radius`, `--pop-trigger-size`, `--pop-trigger-pad`, `--pop-box`, `--pop-count-bg`, `--pop-count-fg`, `--pop-count-size` |
|
|
218
|
+
| `SortableList` | `--sortable-row-h` |
|
|
219
|
+
| `RadialMenu` | `--radial-radius`, `--radial-petal-size`, `--radial-fab-size` |
|
|
218
220
|
|
|
219
221
|
`tests/css-custom-property-contract.test.js` reads this table and fails if a
|
|
220
222
|
listed property is not actually read by its component, so the docs cannot drift.
|
|
@@ -278,7 +280,7 @@ any custom SVG).
|
|
|
278
280
|
filled pin/star toggle), SelectButton, Toggle, OptionButton (`value` +
|
|
279
281
|
`description` secondary text wired via `aria-describedby`, `onfocuschange(value)` on
|
|
280
282
|
focus/hover to drive a preview pane), Modal,
|
|
281
|
-
Popover, Menu (items take a free-form trailing `tag` + `tagTone`, or a `tag` snippet,
|
|
283
|
+
Popover, RadialMenu (a FAB that fans `Menu` items out as petals on an arc, see below), Menu (items take a free-form trailing `tag` + `tagTone`, or a `tag` snippet,
|
|
282
284
|
and an `attrs` object for `data-*`/test ids on the row; a checkable `pressed` item keeps
|
|
283
285
|
its icon leading and shows its check trailing, or uses the leading check slot when it has
|
|
284
286
|
no icon; selecting closes the menu unless `closeOnSelect={false}` or the item sets `keepOpen`;
|
|
@@ -322,6 +324,7 @@ then the leaf ellipsises down to `minLeaf`; `full` skips the fit, `copy` makes i
|
|
|
322
324
|
a copy-on-click button; candidates via the `workingDirCandidates` helper).
|
|
323
325
|
Fieldset (bordered zone, legend rides the border; `droppable` makes it an HTML5
|
|
324
326
|
drop target with `accepts`/`ondrop`/`dropHint` — keyboard alternative is the consumer's job),
|
|
327
|
+
SortableList (reorderable list: grip drag on Pointer Events, keyboard grab, ↑/↓ arrows, see below),
|
|
325
328
|
Combobox (suggestion listbox for a field you render as `children` — an Input for
|
|
326
329
|
plain autocomplete or a Textarea for `@`/`#` mentions; the kit sets the field's
|
|
327
330
|
combobox ARIA and `aria-activedescendant`, floats the `role="listbox"` in the top
|
|
@@ -360,7 +363,10 @@ children are `min-width: 0` so a wide title/actions row can't widen the grid on
|
|
|
360
363
|
mobile), NavBar (horizontal route bar — `<nav>` + `<a aria-current="page">`,
|
|
361
364
|
icon/emoji + label + count badge; `placement="bar"` pins it to the bottom edge
|
|
362
365
|
over `--z-nav` with `--safe-bottom` padding, `orientation` stacks or inlines the
|
|
363
|
-
glyph, `maxWidth` centres the row
|
|
366
|
+
glyph, `maxWidth` centres the row; items without `href` are buttons that set a
|
|
367
|
+
bindable `value` / `onchange`, in a `tablist` when none are links; `center`
|
|
368
|
+
reserves a notch the items split around, for a raised FAB; `variant="floating"`
|
|
369
|
+
insets it as an elevated rounded card), NavItem
|
|
364
370
|
(collapses to an icon rail when the sidebar is narrow; `icon` from the
|
|
365
371
|
registry, `iconPath` / `iconChildren` for custom glyphs, `activeStyle="bar"`
|
|
366
372
|
for the inset-bar active look), MasterDetail (list + detail columns that
|
|
@@ -721,6 +727,41 @@ panel.
|
|
|
721
727
|
]} />
|
|
722
728
|
```
|
|
723
729
|
|
|
730
|
+
### RadialMenu
|
|
731
|
+
|
|
732
|
+
`RadialMenu` is a round FAB that opens its `items` (the same `MenuItem` type
|
|
733
|
+
`Menu` takes: `label`, `icon`, `onselect`, `danger`, `disabled`, `pressed`,
|
|
734
|
+
`keepOpen`, `attrs`) as round icon-over-label petals on an arc around it, over
|
|
735
|
+
a `Scrim` (`scrim={false}` drops it; a click outside still closes). `arc`
|
|
736
|
+
`{ from, to }` is in degrees, 0 = right, counter-clockwise; the default
|
|
737
|
+
`{ from: 200, to: -20 }` is an upward fan. Petals sit at equal angles at
|
|
738
|
+
`radius` (default `--radial-radius`, else `8.5rem`) and are `petalSize` wide
|
|
739
|
+
(default `--radial-petal-size`, else `3.5rem`, never under `--touch-target`).
|
|
740
|
+
The panel lives in the top layer, so it escapes `overflow: hidden` parents and
|
|
741
|
+
a fixed bottom bar; the radius shrinks, and then the fan shifts, to keep every
|
|
742
|
+
petal inside the viewport and its safe-area insets. `status` renders a line
|
|
743
|
+
centred above the arc.
|
|
744
|
+
|
|
745
|
+
The default trigger is a `--radial-fab-size` (3.75rem) circle with a 2px ring
|
|
746
|
+
in `tone` (default `accent`) that swaps `icon` for `closeIcon` while open and
|
|
747
|
+
shows an optional `caption` (string or snippet); `trigger` replaces its content
|
|
748
|
+
and receives `{ open }`. `open` is bindable, with `onopen` / `onclose`; rest
|
|
749
|
+
props land on the trigger. Tap toggles; press-and-slide (press the FAB, drag
|
|
750
|
+
over a petal, release) fires the petal under the finger, sharing the gesture
|
|
751
|
+
helper with `GlyphStack expand="slide"`. The trigger carries `aria-haspopup`,
|
|
752
|
+
`aria-expanded` and `aria-controls`; petals are `menuitem`s with a roving
|
|
753
|
+
tabindex. Opening from the keyboard focuses the first enabled petal; ←/→ walk
|
|
754
|
+
the arc in screen order, ↓/↑ in item order, Home/End jump to the ends,
|
|
755
|
+
Enter/Space fire and close, Escape or Tab close and return focus to the
|
|
756
|
+
trigger. Under `prefers-reduced-motion` petals appear without the staggered
|
|
757
|
+
scale-in.
|
|
758
|
+
|
|
759
|
+
```svelte
|
|
760
|
+
<RadialMenu label="Bot" {items} caption="● bot" tone="ok">
|
|
761
|
+
{#snippet status()}● Armed · explore · in world{/snippet}
|
|
762
|
+
</RadialMenu>
|
|
763
|
+
```
|
|
764
|
+
|
|
724
765
|
### SplitButton
|
|
725
766
|
|
|
726
767
|
`SplitButton` fuses a primary action with a narrow caret that opens a `Menu`:
|
|
@@ -741,6 +782,45 @@ control height so it reads as secondary. ArrowDown on the primary opens the menu
|
|
|
741
782
|
<SplitButton variant="primary" label="Send options" {items} onclick={send}>Send</SplitButton>
|
|
742
783
|
```
|
|
743
784
|
|
|
785
|
+
### SortableList
|
|
786
|
+
|
|
787
|
+
`SortableList` reorders a list the consumer owns. Pass `items` and a `key`, render
|
|
788
|
+
each row's content with the `row` snippet; the kit draws the grip, the ordinal
|
|
789
|
+
(`numbered`) and the ↑/↓ arrows (`arrows`, on by default) around it. `onreorder(keys,
|
|
790
|
+
{ key, from, to })` fires on a drop or an arrow press with the full new key order.
|
|
791
|
+
The list shows that order at once and keeps it until `items` changes; if the
|
|
792
|
+
consumer never adopts it, it falls back to `items` after `revertAfter` ms
|
|
793
|
+
(3000). `canMove(key, to)` vetoes a target; `locked(item)` removes the grip and
|
|
794
|
+
disables the arrows. `selected` (bindable) + `onselect` turn the row body into a
|
|
795
|
+
button with an accent tint and a 2px inset bar on the selected row; put row
|
|
796
|
+
actions in the `trailing` snippet so they stay outside that button.
|
|
797
|
+
|
|
798
|
+
Drag runs on Pointer Events, so it works on touch. The grip has `touch-action:
|
|
799
|
+
none`; `handle="row"` drags from anywhere on the row, and touch then starts
|
|
800
|
+
after a 250 ms long-press so the list still scrolls. The lifted row casts
|
|
801
|
+
`--shadow-md` over a dashed slot, and the nearest scroll container scrolls at
|
|
802
|
+
its edges. On the keyboard the grip is a "Reorder {name}" button: Space/Enter
|
|
803
|
+
grabs, ↑/↓ moves, Home/End jumps, Space/Enter drops, Escape restores.
|
|
804
|
+
Alt+↑/↓ moves the focused row in one step. A polite live region announces each
|
|
805
|
+
step; override any string through `messages` (defaults in
|
|
806
|
+
`SORTABLE_LIST_MESSAGES`). `size` is `sm` (1.875rem rows) or `md` (2.25rem);
|
|
807
|
+
`--sortable-row-h` overrides both.
|
|
808
|
+
|
|
809
|
+
```svelte
|
|
810
|
+
<SortableList
|
|
811
|
+
items={rares}
|
|
812
|
+
key={(r) => r.id}
|
|
813
|
+
name={(r) => r.name}
|
|
814
|
+
label="Rare hunt order"
|
|
815
|
+
numbered
|
|
816
|
+
size="sm"
|
|
817
|
+
bind:selected
|
|
818
|
+
onreorder={(keys) => send({ rare_order: keys })}
|
|
819
|
+
>
|
|
820
|
+
{#snippet row(r)}<span>{r.name}</span><span>{r.dist}</span>{/snippet}
|
|
821
|
+
</SortableList>
|
|
822
|
+
```
|
|
823
|
+
|
|
744
824
|
## Container queries
|
|
745
825
|
|
|
746
826
|
AppShell's `main` and `sidebar` are query containers (`container-name: main` /
|
|
@@ -835,6 +915,19 @@ counter elsewhere.
|
|
|
835
915
|
<Popover label="Filters" count={active}>{#snippet trigger()}<Icon name="filter" />{/snippet}…</Popover>
|
|
836
916
|
```
|
|
837
917
|
|
|
918
|
+
`layout="stacked"` turns a `Button` into a hotbar slot: `glyph` (a registry
|
|
919
|
+
icon name, any text such as `■`, or a snippet) over the label, with heights
|
|
920
|
+
following `size` (sm 2.5rem, md 3rem, lg 3.625rem). `glyph` also leads an
|
|
921
|
+
inline button. `shortcut` paints a faint mono key hint in the top-left corner,
|
|
922
|
+
opposite `count`, and sets `aria-keyshortcuts`; the app still binds the key.
|
|
923
|
+
`tone="danger"` gives a stacked slot a danger border and ink, `pressed` sets
|
|
924
|
+
`aria-pressed` and an accent ring, and coarse pointers get the touch hit slab.
|
|
925
|
+
|
|
926
|
+
```svelte
|
|
927
|
+
<Button layout="stacked" size="sm" glyph="■" shortcut="1" tone="danger" onclick={disarm}>Disarm</Button>
|
|
928
|
+
<Button layout="stacked" size="sm" glyph="search" shortcut="2">Go zone</Button>
|
|
929
|
+
```
|
|
930
|
+
|
|
838
931
|
### Square boxes & touch targets
|
|
839
932
|
|
|
840
933
|
Icon-ish controls share one square scale, `box="xs" | "sm" | "md" | "lg"` =
|
|
@@ -852,10 +945,14 @@ glyphs (`size` stays the SVG px, emoji ×1.35).
|
|
|
852
945
|
On `(pointer: coarse)` every icon-only/square control (and `Popover`'s default
|
|
853
946
|
trigger) carries an invisible `::after` slab that extends its hit area to
|
|
854
947
|
`--touch-target` (44px, WCAG 2.5.8) without moving layout; fine pointers are
|
|
855
|
-
untouched. The slab
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
948
|
+
untouched. The slab is the centred target with its inline-*end* half cut off:
|
|
949
|
+
44px tall, and the box plus half the deficit wide (34px for a 24px icon).
|
|
950
|
+
Inline-end overflow would count towards the scrollable overflow of every scroll
|
|
951
|
+
ancestor, so a button near the right edge of an `overflow-y: auto` container
|
|
952
|
+
would make it scroll sideways; in a row that half lies under the next sibling's
|
|
953
|
+
slab anyway. Growing the whole deficit towards the start instead would let each
|
|
954
|
+
slab cover the previous sibling's centre, so taps would land one icon to the
|
|
955
|
+
right.
|
|
859
956
|
`hitArea="compact"` opts a dense-table row out. `Button
|
|
860
957
|
collapseLabel="mobile" | "container"` hides `<span data-label>` children below
|
|
861
958
|
40rem viewport / 30rem container, drops to icon padding, and names the button
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import Icon, { isIconName } from './Icon.svelte';
|
|
2
3
|
import { formatCount, hasCount } from '../../count';
|
|
3
4
|
import type { ControlSize } from '../../size';
|
|
4
5
|
import { canonicalTone, type Tone } from '../../tone';
|
|
@@ -51,7 +52,7 @@
|
|
|
51
52
|
// container (`container`) so the button becomes icon-only; the hidden text
|
|
52
53
|
// becomes the aria-label unless one is set.
|
|
53
54
|
collapseLabel?: 'never' | 'mobile' | 'container';
|
|
54
|
-
// Icon-only/square buttons grow a
|
|
55
|
+
// Icon-only/square buttons grow a touch hit slab on coarse pointers without
|
|
55
56
|
// moving layout; `compact` opts out in dense tables.
|
|
56
57
|
hitArea?: 'auto' | 'compact';
|
|
57
58
|
// Async/busy state: shows a spinner, blocks clicks, sets aria-busy. Stays
|
|
@@ -62,6 +63,16 @@
|
|
|
62
63
|
// exact number. Tint via `--btn-count-bg` / `--btn-count-fg`.
|
|
63
64
|
count?: number;
|
|
64
65
|
countMax?: number;
|
|
66
|
+
// `stacked`: a hotbar slot, `glyph` over the label, height following `size`.
|
|
67
|
+
layout?: 'inline' | 'stacked';
|
|
68
|
+
// Leading glyph (above the label when stacked): a registry icon name, any
|
|
69
|
+
// other text (emoji, ■), or a snippet.
|
|
70
|
+
glyph?: Snippet | string;
|
|
71
|
+
// Key hint in the top-left corner, also exposed as `aria-keyshortcuts`.
|
|
72
|
+
// Display only: the app binds the key.
|
|
73
|
+
shortcut?: string;
|
|
74
|
+
// Two-state toggle: sets `aria-pressed`.
|
|
75
|
+
pressed?: boolean;
|
|
65
76
|
class?: string;
|
|
66
77
|
children?: Snippet;
|
|
67
78
|
};
|
|
@@ -88,6 +99,10 @@
|
|
|
88
99
|
loading = false,
|
|
89
100
|
count,
|
|
90
101
|
countMax = 99,
|
|
102
|
+
layout = 'inline',
|
|
103
|
+
glyph,
|
|
104
|
+
shortcut,
|
|
105
|
+
pressed,
|
|
91
106
|
type = 'button',
|
|
92
107
|
disabled = false,
|
|
93
108
|
title,
|
|
@@ -114,6 +129,9 @@
|
|
|
114
129
|
// folded into it; without one the sr-only span below is read after the label.
|
|
115
130
|
const withCount = (name: string) => (counted ? `${name}, ${count}` : name);
|
|
116
131
|
|
|
132
|
+
const stacked = $derived(layout === 'stacked');
|
|
133
|
+
const keyHint = $derived(shortcut?.trim() || undefined);
|
|
134
|
+
|
|
117
135
|
let el = $state<HTMLElement | null>(null);
|
|
118
136
|
const explicitName = $derived(rest['aria-label'] ?? rest['aria-labelledby']);
|
|
119
137
|
const ariaLabel = $derived(rest['aria-label'] ? withCount(rest['aria-label']) : undefined);
|
|
@@ -136,6 +154,8 @@
|
|
|
136
154
|
{...elementAttrs}
|
|
137
155
|
aria-label={ariaLabel}
|
|
138
156
|
aria-busy={loading || undefined}
|
|
157
|
+
aria-pressed={pressed ?? rest['aria-pressed']}
|
|
158
|
+
aria-keyshortcuts={rest['aria-keyshortcuts'] ?? keyHint}
|
|
139
159
|
{title}
|
|
140
160
|
class="btn {klass}"
|
|
141
161
|
class:btn-grow={grow}
|
|
@@ -166,10 +186,22 @@
|
|
|
166
186
|
class:hover-danger={hoverDanger}
|
|
167
187
|
class:loading
|
|
168
188
|
class:btn-counted={counted}
|
|
189
|
+
class:btn-stacked={stacked}
|
|
190
|
+
class:btn-hinted={keyHint !== undefined}
|
|
169
191
|
onclick={onclick}
|
|
170
192
|
>
|
|
171
193
|
{#if loading}<span class="btn-spinner" aria-hidden="true"></span>{/if}
|
|
172
|
-
{
|
|
194
|
+
{#if glyph !== undefined}
|
|
195
|
+
<span class="btn-glyph" aria-hidden="true">
|
|
196
|
+
{#if typeof glyph !== 'string'}{@render glyph()}{:else if isIconName(glyph)}<Icon name={glyph} />{:else}{glyph}{/if}
|
|
197
|
+
</span>
|
|
198
|
+
{/if}
|
|
199
|
+
{#if stacked}
|
|
200
|
+
<span class="btn-stack-label">{@render children?.()}</span>
|
|
201
|
+
{:else}
|
|
202
|
+
{@render children?.()}
|
|
203
|
+
{/if}
|
|
204
|
+
{#if keyHint}<span class="btn-shortcut" aria-hidden="true">{keyHint}</span>{/if}
|
|
173
205
|
{#if counted}
|
|
174
206
|
<span class="btn-count" aria-hidden="true">{countText}</span>
|
|
175
207
|
<span class="btn-count-sr">, {count}</span>
|
|
@@ -507,29 +539,31 @@
|
|
|
507
539
|
}
|
|
508
540
|
|
|
509
541
|
/* Coarse pointers: icon-only and square buttons carry an invisible slab that
|
|
510
|
-
extends the hit area
|
|
542
|
+
extends the hit area towards --touch-target without changing layout. Buttons
|
|
511
543
|
already at or above the target keep their own box (inset never positive).
|
|
512
|
-
The slab
|
|
513
|
-
towards the scrollable overflow of every scroll ancestor,
|
|
514
|
-
|
|
515
|
-
|
|
544
|
+
The slab is the centred target with its inline-end half cut off: inline-end
|
|
545
|
+
overflow counts towards the scrollable overflow of every scroll ancestor, and
|
|
546
|
+
in a row that half lies under the next sibling anyway. Growing further towards
|
|
547
|
+
the start would let each slab cover the previous sibling's centre. */
|
|
516
548
|
@media (pointer: coarse) {
|
|
517
549
|
.btn-icon:not(.hit-compact),
|
|
518
550
|
.btn-icon-inline:not(.hit-compact),
|
|
519
551
|
.btn-box:not(.hit-compact),
|
|
520
552
|
.btn-square:not(.hit-compact),
|
|
521
|
-
.btn-chip:not(.hit-compact)
|
|
553
|
+
.btn-chip:not(.hit-compact),
|
|
554
|
+
.btn-stacked:not(.hit-compact) {
|
|
522
555
|
position: relative;
|
|
523
556
|
}
|
|
524
557
|
.btn-icon:not(.hit-compact)::after,
|
|
525
558
|
.btn-icon-inline:not(.hit-compact)::after,
|
|
526
559
|
.btn-box:not(.hit-compact)::after,
|
|
527
560
|
.btn-square:not(.hit-compact)::after,
|
|
528
|
-
.btn-chip:not(.hit-compact)::after
|
|
561
|
+
.btn-chip:not(.hit-compact)::after,
|
|
562
|
+
.btn-stacked:not(.hit-compact)::after {
|
|
529
563
|
content: '';
|
|
530
564
|
position: absolute;
|
|
531
565
|
inset-block: min(0px, calc((100% - var(--touch-target)) / 2));
|
|
532
|
-
inset-inline-start: min(0px, calc(100% - var(--touch-target)));
|
|
566
|
+
inset-inline-start: min(0px, calc((100% - var(--touch-target)) / 2));
|
|
533
567
|
inset-inline-end: 0;
|
|
534
568
|
}
|
|
535
569
|
}
|
|
@@ -541,6 +575,80 @@
|
|
|
541
575
|
color: var(--btn-on, var(--accent));
|
|
542
576
|
}
|
|
543
577
|
|
|
578
|
+
/* Stacked hotbar slot: glyph over a short label. Follows the size and
|
|
579
|
+
control rules so its own heights win at equal specificity. */
|
|
580
|
+
.btn-stacked,
|
|
581
|
+
.btn-stacked.btn-sm,
|
|
582
|
+
.btn-stacked.btn-lg,
|
|
583
|
+
.btn-stacked.btn-control {
|
|
584
|
+
flex-direction: column;
|
|
585
|
+
gap: var(--sp-1);
|
|
586
|
+
height: auto;
|
|
587
|
+
min-height: var(--btn-size, var(--control-height-large));
|
|
588
|
+
min-width: var(--btn-size, var(--control-height-large));
|
|
589
|
+
padding: var(--sp-1) var(--sp-2);
|
|
590
|
+
font-size: var(--fs-xs);
|
|
591
|
+
}
|
|
592
|
+
.btn-stacked.btn-sm {
|
|
593
|
+
min-height: var(--btn-size, var(--control-height-default));
|
|
594
|
+
min-width: var(--btn-size, calc(var(--control-height-default) + var(--sp-1) + 0.125rem));
|
|
595
|
+
gap: 0.1875rem;
|
|
596
|
+
padding: var(--sp-1);
|
|
597
|
+
font-size: calc(var(--fs-xs) * 0.85);
|
|
598
|
+
}
|
|
599
|
+
.btn-stacked.btn-lg {
|
|
600
|
+
min-height: var(--btn-size, calc(var(--control-height-large) + var(--sp-2) + 0.125rem));
|
|
601
|
+
min-width: var(--btn-size, calc(var(--control-height-large) + var(--sp-2) + 0.125rem));
|
|
602
|
+
padding: var(--sp-2) var(--sp-3);
|
|
603
|
+
font-size: var(--fs-sm);
|
|
604
|
+
}
|
|
605
|
+
.btn-glyph {
|
|
606
|
+
display: inline-flex;
|
|
607
|
+
align-items: center;
|
|
608
|
+
justify-content: center;
|
|
609
|
+
flex: none;
|
|
610
|
+
line-height: 1;
|
|
611
|
+
}
|
|
612
|
+
.btn-stacked .btn-glyph {
|
|
613
|
+
font-size: var(--fs-lg);
|
|
614
|
+
}
|
|
615
|
+
.btn-stacked.btn-sm .btn-glyph {
|
|
616
|
+
font-size: var(--fs-md);
|
|
617
|
+
}
|
|
618
|
+
.btn-stacked.btn-lg .btn-glyph {
|
|
619
|
+
font-size: var(--fs-xl);
|
|
620
|
+
}
|
|
621
|
+
.btn-stack-label {
|
|
622
|
+
max-width: 100%;
|
|
623
|
+
overflow: hidden;
|
|
624
|
+
text-overflow: ellipsis;
|
|
625
|
+
}
|
|
626
|
+
.btn-stacked.btn-danger,
|
|
627
|
+
.btn-stacked.btn-tone-danger {
|
|
628
|
+
color: var(--danger);
|
|
629
|
+
border-color: var(--danger);
|
|
630
|
+
}
|
|
631
|
+
.btn-stacked[aria-pressed='true'] {
|
|
632
|
+
border-color: var(--btn-on, var(--accent));
|
|
633
|
+
box-shadow: inset 0 0 0 1px var(--btn-on, var(--accent));
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/* Corner key hint: mirrors the count pill in the opposite corner, inside the box. */
|
|
637
|
+
.btn-hinted {
|
|
638
|
+
position: relative;
|
|
639
|
+
}
|
|
640
|
+
.btn-shortcut {
|
|
641
|
+
position: absolute;
|
|
642
|
+
top: 0.125rem;
|
|
643
|
+
left: 0.25rem;
|
|
644
|
+
font-family: var(--font-mono);
|
|
645
|
+
font-size: calc(var(--fs-xs) * 0.75);
|
|
646
|
+
font-weight: var(--fw-medium);
|
|
647
|
+
color: var(--text-faint);
|
|
648
|
+
line-height: 1;
|
|
649
|
+
pointer-events: none;
|
|
650
|
+
}
|
|
651
|
+
|
|
544
652
|
/* Loading: keep full opacity (it's busy, not disabled-looking) + a wait cursor,
|
|
545
653
|
and show a spinner in the current text color. */
|
|
546
654
|
.btn.loading {
|
|
@@ -26,6 +26,10 @@ type ButtonProps = HTMLButtonAttributes & {
|
|
|
26
26
|
loading?: boolean;
|
|
27
27
|
count?: number;
|
|
28
28
|
countMax?: number;
|
|
29
|
+
layout?: 'inline' | 'stacked';
|
|
30
|
+
glyph?: Snippet | string;
|
|
31
|
+
shortcut?: string;
|
|
32
|
+
pressed?: boolean;
|
|
29
33
|
class?: string;
|
|
30
34
|
children?: Snippet;
|
|
31
35
|
};
|