@orianatech/pire 0.2.1 → 0.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/README.md +43 -5
- package/dist/components/core/Icon.d.cts +22 -7
- package/dist/components/core/Icon.d.ts +22 -7
- package/dist/components/core/Icon.d.ts.map +1 -1
- package/dist/components/core/icon-data.generated.d.cts +1 -0
- package/dist/components/core/icon-data.generated.d.ts +2 -0
- package/dist/components/core/icon-data.generated.d.ts.map +1 -0
- package/dist/components/core/icon-map.d.cts +13 -0
- package/dist/components/core/icon-map.d.ts +14 -0
- package/dist/components/core/icon-map.d.ts.map +1 -0
- package/dist/components/feedback/Skeleton.d.cts +43 -0
- package/dist/components/feedback/Skeleton.d.ts +44 -0
- package/dist/components/feedback/Skeleton.d.ts.map +1 -0
- package/dist/components/forms/MultiComboBox.d.cts +58 -0
- package/dist/components/forms/MultiComboBox.d.ts +59 -0
- package/dist/components/forms/MultiComboBox.d.ts.map +1 -0
- package/dist/components/forms/TextArea.d.cts +42 -0
- package/dist/components/forms/TextArea.d.ts +43 -0
- package/dist/components/forms/TextArea.d.ts.map +1 -0
- package/dist/components/navigation/ShellBar.d.cts +8 -1
- package/dist/components/navigation/ShellBar.d.ts +8 -1
- package/dist/components/navigation/ShellBar.d.ts.map +1 -1
- package/dist/components/navigation/SideNav.d.cts +56 -4
- package/dist/components/navigation/SideNav.d.ts +56 -4
- package/dist/components/navigation/SideNav.d.ts.map +1 -1
- package/dist/components.css +73 -1
- package/dist/index.cjs +806 -353
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +809 -347
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
|
@@ -1,9 +1,24 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
/** A second-level destination, nested under a first-level entry.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately has no `icon`: only the first level carries one, so the second level reads as an
|
|
5
|
+
* indented list of labels under a single glyph rather than a wall of decoration. Leaving `icon` off
|
|
6
|
+
* the *type* is what stops that drifting back in one call site at a time. */
|
|
7
|
+
export interface SideNavChildItem {
|
|
8
|
+
id: string;
|
|
9
|
+
label: string;
|
|
10
|
+
count?: number;
|
|
11
|
+
}
|
|
2
12
|
export interface SideNavItem {
|
|
3
13
|
id: string;
|
|
4
14
|
label: string;
|
|
5
15
|
icon?: string;
|
|
6
16
|
count?: number;
|
|
17
|
+
/** Second-level destinations. Supplying this turns the entry into a collapsible group: it stops
|
|
18
|
+
* being a destination itself (pressing it expands, never navigates) and `id` becomes the
|
|
19
|
+
* expansion key used by `expandedIds` / `defaultExpandedIds`, not something `onChange` can ever
|
|
20
|
+
* emit. Omit it and the entry behaves exactly as it always has. */
|
|
21
|
+
items?: SideNavChildItem[];
|
|
7
22
|
}
|
|
8
23
|
export interface SideNavGroup {
|
|
9
24
|
label?: string;
|
|
@@ -13,10 +28,47 @@ export interface SideNavProps extends Omit<React.HTMLAttributes<HTMLElement>, 'o
|
|
|
13
28
|
/** Spaces-and-pages hierarchy: one group per space. */
|
|
14
29
|
groups: SideNavGroup[];
|
|
15
30
|
value?: string;
|
|
16
|
-
/** Fires with the selected item id — not a DOM change event.
|
|
31
|
+
/** Fires with the selected item id — not a DOM change event. Only ever a destination id: a
|
|
32
|
+
* first-level entry that owns `items` is a disclosure, and toggling it does not fire this. */
|
|
17
33
|
onChange?: (id: string) => void;
|
|
18
|
-
/**
|
|
34
|
+
/** Expanded first-level entry ids — the controlled half of the pair. Pass it and you own expansion
|
|
35
|
+
* outright, including expanding the group that holds `value`. */
|
|
36
|
+
expandedIds?: string[];
|
|
37
|
+
/** Initial expanded ids for the uncontrolled case. Omit it and the group holding `value` opens
|
|
38
|
+
* itself; supply it and it is taken literally, so `value`'s group can start closed if that is
|
|
39
|
+
* genuinely what you want. */
|
|
40
|
+
defaultExpandedIds?: string[];
|
|
41
|
+
/** Fires with every expanded id after a toggle, controlled or not. */
|
|
42
|
+
onExpandedChange?: (ids: string[]) => void;
|
|
43
|
+
/** 48px icon rail. Labels move into tooltips, and second-level items are not rendered at all —
|
|
44
|
+
* see the note on {@link SideNav}. */
|
|
19
45
|
collapsed?: boolean;
|
|
20
46
|
footer?: React.ReactNode;
|
|
21
47
|
}
|
|
22
|
-
|
|
48
|
+
/** Primary navigation. A first-level entry is either a destination (icon + label, selectable) or a
|
|
49
|
+
* collapsible group (icon + label + chevron) when it carries `items` — the two coexist in one list,
|
|
50
|
+
* which is how a 35-destination ERP menu fits without showing all 35 at once.
|
|
51
|
+
*
|
|
52
|
+
* Both levels are plain `Button`s and the disclosure is React Aria's `Disclosure` /
|
|
53
|
+
* `DisclosureGroup`, so `aria-expanded`, `aria-controls`, the panel's `role="group"` and
|
|
54
|
+
* `aria-labelledby`, and Enter/Space on the trigger all come from React Aria rather than from
|
|
55
|
+
* hand-rolled ARIA here.
|
|
56
|
+
*
|
|
57
|
+
* Three things worth knowing before you use it:
|
|
58
|
+
*
|
|
59
|
+
* - **The panel is always in the DOM.** React Aria hides a collapsed panel with
|
|
60
|
+
* `hidden="until-found"`, applied imperatively in a layout effect, so find-in-page can reveal it.
|
|
61
|
+
* A test asserting a collapsed child is *absent* will fail; assert `aria-expanded` on the trigger,
|
|
62
|
+
* or visibility, instead. The corollary in CSS: any `display` we set on the panel would beat the
|
|
63
|
+
* UA's `[hidden] { display: none }`, leaving collapsed children focusable under
|
|
64
|
+
* `aria-hidden="true"`. `.pire-sidenav-subnav[hidden]` in `components.css` is that guard.
|
|
65
|
+
* - **`collapsed` and expansion are different axes, and the rail wins.** With `collapsed` set there
|
|
66
|
+
* are no labels, and a second level is nothing *but* labels, so children are not rendered in the
|
|
67
|
+
* rail. A group's row becomes an ordinary icon button that records the expansion (so the group is
|
|
68
|
+
* already open when the rail expands) and lights up while it holds `value`, which is the one thing
|
|
69
|
+
* a rail must still communicate. It follows that a nav with collapsible groups needs a way back
|
|
70
|
+
* out of the rail — `ShellBar`'s `onMenu` — or 25 of those 35 destinations are unreachable, for
|
|
71
|
+
* everyone, exactly as the labels already are.
|
|
72
|
+
* - **Several groups can be open at once**, and there is no accordion mode. Closing the group you
|
|
73
|
+
* are in to open another is the wrong trade in a nav whose whole job is showing where you are. */
|
|
74
|
+
export declare function SideNav({ groups, value, onChange, expandedIds, defaultExpandedIds, onExpandedChange, collapsed, footer, className, ...rest }: SideNavProps): React.JSX.Element;
|
|
@@ -1,9 +1,24 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
/** A second-level destination, nested under a first-level entry.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately has no `icon`: only the first level carries one, so the second level reads as an
|
|
5
|
+
* indented list of labels under a single glyph rather than a wall of decoration. Leaving `icon` off
|
|
6
|
+
* the *type* is what stops that drifting back in one call site at a time. */
|
|
7
|
+
export interface SideNavChildItem {
|
|
8
|
+
id: string;
|
|
9
|
+
label: string;
|
|
10
|
+
count?: number;
|
|
11
|
+
}
|
|
2
12
|
export interface SideNavItem {
|
|
3
13
|
id: string;
|
|
4
14
|
label: string;
|
|
5
15
|
icon?: string;
|
|
6
16
|
count?: number;
|
|
17
|
+
/** Second-level destinations. Supplying this turns the entry into a collapsible group: it stops
|
|
18
|
+
* being a destination itself (pressing it expands, never navigates) and `id` becomes the
|
|
19
|
+
* expansion key used by `expandedIds` / `defaultExpandedIds`, not something `onChange` can ever
|
|
20
|
+
* emit. Omit it and the entry behaves exactly as it always has. */
|
|
21
|
+
items?: SideNavChildItem[];
|
|
7
22
|
}
|
|
8
23
|
export interface SideNavGroup {
|
|
9
24
|
label?: string;
|
|
@@ -13,11 +28,48 @@ export interface SideNavProps extends Omit<React.HTMLAttributes<HTMLElement>, 'o
|
|
|
13
28
|
/** Spaces-and-pages hierarchy: one group per space. */
|
|
14
29
|
groups: SideNavGroup[];
|
|
15
30
|
value?: string;
|
|
16
|
-
/** Fires with the selected item id — not a DOM change event.
|
|
31
|
+
/** Fires with the selected item id — not a DOM change event. Only ever a destination id: a
|
|
32
|
+
* first-level entry that owns `items` is a disclosure, and toggling it does not fire this. */
|
|
17
33
|
onChange?: (id: string) => void;
|
|
18
|
-
/**
|
|
34
|
+
/** Expanded first-level entry ids — the controlled half of the pair. Pass it and you own expansion
|
|
35
|
+
* outright, including expanding the group that holds `value`. */
|
|
36
|
+
expandedIds?: string[];
|
|
37
|
+
/** Initial expanded ids for the uncontrolled case. Omit it and the group holding `value` opens
|
|
38
|
+
* itself; supply it and it is taken literally, so `value`'s group can start closed if that is
|
|
39
|
+
* genuinely what you want. */
|
|
40
|
+
defaultExpandedIds?: string[];
|
|
41
|
+
/** Fires with every expanded id after a toggle, controlled or not. */
|
|
42
|
+
onExpandedChange?: (ids: string[]) => void;
|
|
43
|
+
/** 48px icon rail. Labels move into tooltips, and second-level items are not rendered at all —
|
|
44
|
+
* see the note on {@link SideNav}. */
|
|
19
45
|
collapsed?: boolean;
|
|
20
46
|
footer?: React.ReactNode;
|
|
21
47
|
}
|
|
22
|
-
|
|
48
|
+
/** Primary navigation. A first-level entry is either a destination (icon + label, selectable) or a
|
|
49
|
+
* collapsible group (icon + label + chevron) when it carries `items` — the two coexist in one list,
|
|
50
|
+
* which is how a 35-destination ERP menu fits without showing all 35 at once.
|
|
51
|
+
*
|
|
52
|
+
* Both levels are plain `Button`s and the disclosure is React Aria's `Disclosure` /
|
|
53
|
+
* `DisclosureGroup`, so `aria-expanded`, `aria-controls`, the panel's `role="group"` and
|
|
54
|
+
* `aria-labelledby`, and Enter/Space on the trigger all come from React Aria rather than from
|
|
55
|
+
* hand-rolled ARIA here.
|
|
56
|
+
*
|
|
57
|
+
* Three things worth knowing before you use it:
|
|
58
|
+
*
|
|
59
|
+
* - **The panel is always in the DOM.** React Aria hides a collapsed panel with
|
|
60
|
+
* `hidden="until-found"`, applied imperatively in a layout effect, so find-in-page can reveal it.
|
|
61
|
+
* A test asserting a collapsed child is *absent* will fail; assert `aria-expanded` on the trigger,
|
|
62
|
+
* or visibility, instead. The corollary in CSS: any `display` we set on the panel would beat the
|
|
63
|
+
* UA's `[hidden] { display: none }`, leaving collapsed children focusable under
|
|
64
|
+
* `aria-hidden="true"`. `.pire-sidenav-subnav[hidden]` in `components.css` is that guard.
|
|
65
|
+
* - **`collapsed` and expansion are different axes, and the rail wins.** With `collapsed` set there
|
|
66
|
+
* are no labels, and a second level is nothing *but* labels, so children are not rendered in the
|
|
67
|
+
* rail. A group's row becomes an ordinary icon button that records the expansion (so the group is
|
|
68
|
+
* already open when the rail expands) and lights up while it holds `value`, which is the one thing
|
|
69
|
+
* a rail must still communicate. It follows that a nav with collapsible groups needs a way back
|
|
70
|
+
* out of the rail — `ShellBar`'s `onMenu` — or 25 of those 35 destinations are unreachable, for
|
|
71
|
+
* everyone, exactly as the labels already are.
|
|
72
|
+
* - **Several groups can be open at once**, and there is no accordion mode. Closing the group you
|
|
73
|
+
* are in to open another is the wrong trade in a nav whose whole job is showing where you are. */
|
|
74
|
+
export declare function SideNav({ groups, value, onChange, expandedIds, defaultExpandedIds, onExpandedChange, collapsed, footer, className, ...rest }: SideNavProps): React.JSX.Element;
|
|
23
75
|
//# sourceMappingURL=SideNav.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SideNav.d.ts","sourceRoot":"","sources":["../../../src/components/navigation/SideNav.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,
|
|
1
|
+
{"version":3,"file":"SideNav.d.ts","sourceRoot":"","sources":["../../../src/components/navigation/SideNav.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAO/B;;;;8EAI8E;AAC9E,MAAM,WAAW,gBAAgB;IAAG,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE;AAE/E,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;wEAGoE;IACpE,KAAK,CAAC,EAAE,gBAAgB,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAAG,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,EAAE,CAAA;CAAE;AAEtE,MAAM,WAAW,YAAa,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,WAAW,CAAC,EAAE,UAAU,CAAC;IACvF,uDAAuD;IACvD,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;mGAC+F;IAC/F,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC;sEACkE;IAClE,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB;;mCAE+B;IAC/B,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,sEAAsE;IACtE,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC3C;2CACuC;IACvC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC1B;AAaD;;;;;;;;;;;;;;;;;;;;;;;;;qGAyBqG;AACrG,wBAAgB,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,kBAAkB,EAAE,gBAAgB,EAClG,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,YAAY,qBAwGtD"}
|
package/dist/components.css
CHANGED
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
.pire-card-body{padding:var(--sp-4);flex:1}
|
|
60
60
|
.pire-card-foot{display:flex;justify-content:flex-end;gap:var(--sp-2);padding:var(--sp-3) var(--sp-4);border-top:1px solid var(--border-subtle)}
|
|
61
61
|
|
|
62
|
-
/* ---- Field shell (Input, Select, ComboBox)
|
|
62
|
+
/* ---- Field shell (Input, Select, ComboBox, TextArea, MultiComboBox) --- */
|
|
63
63
|
.pire-field{display:flex;flex-direction:column;gap:var(--sp-1)}
|
|
64
64
|
.pire-field-label{font:var(--type-label);color:var(--text-label)}
|
|
65
65
|
.pire-field-req{color:var(--status-negative-fg);margin-left:var(--sp-0h)}
|
|
@@ -90,6 +90,30 @@
|
|
|
90
90
|
.pire-input::placeholder{color:var(--text-tertiary)}
|
|
91
91
|
.pire-affix{font:var(--type-caption);color:var(--text-secondary);flex:0 0 auto}
|
|
92
92
|
.pire-leading-icon{color:var(--text-tertiary);flex:0 0 auto}
|
|
93
|
+
/* The shell is fixed-height by default because every single-line control is. Controls whose
|
|
94
|
+
content sets the height (TextArea, MultiComboBox's chips) opt out with data-multiline and
|
|
95
|
+
keep everything else — focus ring, invalid, disabled, readonly, sizing — from the rules
|
|
96
|
+
above. This block sits after the [data-size] heights on purpose: equal specificity, so the
|
|
97
|
+
later `height:auto` wins, and the per-size floor is restored as a min-height. */
|
|
98
|
+
.pire-control[data-multiline="true"]{height:auto;min-height:var(--control-h-md);align-items:stretch;padding-top:var(--sp-2);padding-bottom:var(--sp-2)}
|
|
99
|
+
.pire-control[data-multiline="true"][data-size="sm"]{min-height:var(--control-h-sm)}
|
|
100
|
+
.pire-control[data-multiline="true"][data-size="lg"]{min-height:var(--control-h-lg)}
|
|
101
|
+
|
|
102
|
+
/* ---- TextArea -------------------------------------------------------- */
|
|
103
|
+
.pire-textarea{display:block;width:100%;resize:vertical;overflow:auto}
|
|
104
|
+
/* autoGrow writes an inline height on every keystroke, so a height dragged by hand would be
|
|
105
|
+
thrown away on the next one — the grip is removed rather than left lying about it. */
|
|
106
|
+
.pire-textarea[data-auto-grow="true"]{resize:none}
|
|
107
|
+
|
|
108
|
+
/* ---- MultiComboBox --------------------------------------------------- */
|
|
109
|
+
.pire-chipfield{flex:1;min-width:0;display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-1)}
|
|
110
|
+
/* Never narrower than a few characters, so the caret stays reachable however many chips are in. */
|
|
111
|
+
.pire-chipinput{flex:1 1 6ch;min-width:6ch}
|
|
112
|
+
/* Each chip's remove button is a Tab stop of its own inside the shell, and the shell's blanket
|
|
113
|
+
`:focus-visible{outline:none}` (see above) would otherwise leave it unmarked. */
|
|
114
|
+
.pire-chipfield .pire-tag-remove[data-focus-visible]{outline:var(--focus-w) solid var(--focus-color);outline-offset:var(--focus-offset)}
|
|
115
|
+
/* One ring at a time: while a chip owns focus it, not the shell, is what moved. */
|
|
116
|
+
.pire-control:has(.pire-tag-remove[data-focus-visible])[data-focus-within]{outline:none}
|
|
93
117
|
|
|
94
118
|
/* ---- Select / ListBox popover ---------------------------------------- */
|
|
95
119
|
.pire-select-value{flex:1;min-width:0;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font:var(--type-body)}
|
|
@@ -164,6 +188,28 @@
|
|
|
164
188
|
.pire-sidenav-item[data-hovered]{background:var(--surface-hover)}
|
|
165
189
|
.pire-sidenav-item[data-selected="true"]{background:var(--surface-selected);border-left-color:var(--accent-bg);color:var(--accent-subtle-fg);font:var(--type-body-strong)}
|
|
166
190
|
.pire-sidenav-count{margin-left:auto;font:var(--type-code);color:var(--text-tertiary)}
|
|
191
|
+
/* Second level. `-tree` is React Aria's DisclosureGroup, `-section` one Disclosure; both exist only
|
|
192
|
+
to keep the rows in a column, since the wrappers are divs the component cannot avoid. */
|
|
193
|
+
.pire-sidenav-tree,.pire-sidenav-section{display:flex;flex-direction:column}
|
|
194
|
+
/* The trailing chevron. Its own auto margin pushes it to the edge on a group row with no count; when
|
|
195
|
+
there is a count, the count's auto margin has already claimed the free space and a second auto here
|
|
196
|
+
would split it and leave the count floating mid-row — hence the reset. */
|
|
197
|
+
.pire-sidenav-chevron{margin-left:auto;color:var(--text-tertiary);transition:transform var(--dur-fast) var(--ease-standard)}
|
|
198
|
+
.pire-sidenav-count + .pire-sidenav-chevron{margin-left:0}
|
|
199
|
+
.pire-sidenav-section:not([data-expanded]) .pire-sidenav-chevron{transform:rotate(-90deg)}
|
|
200
|
+
/* Marks a group row whose child is the current destination: the accent tick and label weight of a
|
|
201
|
+
selection without its fill, so the trail to `aria-current` reads at a glance and still loses to it.
|
|
202
|
+
Also the only cue left in the icon rail, where the children are not rendered. */
|
|
203
|
+
.pire-sidenav-item[data-active-child="true"]{border-left-color:var(--accent-bg);color:var(--accent-subtle-fg);font:var(--type-body-strong)}
|
|
204
|
+
/* React Aria animates the panel through --disclosure-panel-height, and applies `hidden` only once the
|
|
205
|
+
animation settles. The `display` above therefore has to be undone for the collapsed state: it beats
|
|
206
|
+
the UA's `[hidden]{display:none}`, and a visible-to-the-layout panel under `aria-hidden="true"`
|
|
207
|
+
leaves every child in the tab order — an aria-hidden-focus defect, not just a cosmetic one. */
|
|
208
|
+
.pire-sidenav-subnav{display:flex;flex-direction:column;overflow:hidden;height:var(--disclosure-panel-height,auto);transition:height var(--dur-fast) var(--ease-standard)}
|
|
209
|
+
.pire-sidenav-subnav[hidden]{display:none}
|
|
210
|
+
/* Indented to the parent's *label*, not its icon: page padding + the 16px glyph box + the row gap.
|
|
211
|
+
Condensed height because a two-level menu is 35 rows, not 10. */
|
|
212
|
+
.pire-sidenav-subitem{height:var(--row-h-condensed);padding-left:calc(var(--sp-4) + var(--sp-4) + var(--sp-3))}
|
|
167
213
|
.pire-sidenav-footer{margin-top:auto;padding:var(--sp-3) var(--sp-4);border-top:1px solid var(--border-subtle)}
|
|
168
214
|
|
|
169
215
|
/* ---- Feedback -------------------------------------------------------- */
|
|
@@ -203,6 +249,28 @@
|
|
|
203
249
|
.pire-progress[data-status="negative"] .pire-progress-fill{background:var(--status-negative-fg)}
|
|
204
250
|
.pire-tooltip{max-width:240px;padding:var(--sp-1) var(--sp-2);border-radius:var(--radius-1);background:var(--gray-90);color:var(--text-inverse);font:var(--type-caption);box-shadow:var(--shadow-2);animation:pire-fade-in var(--dur-fast) var(--ease-entrance)}
|
|
205
251
|
|
|
252
|
+
/* ---- Skeleton ---------------------------------------------------------- */
|
|
253
|
+
/* The sweep is a lightness *difference* between two surface aliases, not a fixed direction:
|
|
254
|
+
--surface-hover is lighter than --surface-active in the light theme and darker in the dark
|
|
255
|
+
one, and either reads as a sweep. That keeps the placeholder on the shared alias layer
|
|
256
|
+
instead of introducing a skeleton-only token pair for both themes to maintain.
|
|
257
|
+
The duration is derived, not literal: a shimmer at --dur-slow (400ms) is agitated rather
|
|
258
|
+
than patient, and 3x is the slowest step the motion scale can express. */
|
|
259
|
+
.pire-skeleton{display:block;width:100%;background-color:var(--surface-active);background-image:linear-gradient(90deg,transparent,var(--surface-hover),transparent);background-size:200% 100%;background-repeat:no-repeat;border-radius:var(--radius-1);animation:pire-skeleton-sweep calc(var(--dur-slow) * 3) var(--ease-standard) infinite}
|
|
260
|
+
.pire-skeleton[data-shape="text"]{height:var(--fs-body)}
|
|
261
|
+
.pire-skeleton[data-shape="block"]{height:var(--sp-16);border-radius:var(--radius-2)}
|
|
262
|
+
.pire-skeleton[data-shape="circle"]{width:var(--sp-8);height:var(--sp-8);border-radius:var(--radius-pill);flex:0 0 auto}
|
|
263
|
+
@keyframes pire-skeleton-sweep{from{background-position:150% 0}to{background-position:-50% 0}}
|
|
264
|
+
/* Geometry copied from .pire-tr / .pire-td, not approximated, so a loading table and a loaded
|
|
265
|
+
one are the same height and the rows do not jump when the data lands. */
|
|
266
|
+
.pire-skeleton-row{display:flex;align-items:center;height:var(--row-h-regular);border-bottom:1px solid var(--border-subtle)}
|
|
267
|
+
.pire-skeleton-row[data-density="condensed"]{height:var(--row-h-condensed)}
|
|
268
|
+
.pire-skeleton-row[data-density="comfortable"]{height:var(--row-h-comfortable)}
|
|
269
|
+
.pire-skeleton-cell{flex:1 1 0;min-width:0;display:flex;padding:0 var(--sp-3)}
|
|
270
|
+
.pire-skeleton-row[data-density="condensed"] .pire-skeleton-cell{padding:0 var(--sp-2)}
|
|
271
|
+
.pire-skeleton-cell[data-align="right"]{justify-content:flex-end}
|
|
272
|
+
.pire-skeleton-cell[data-align="center"]{justify-content:center}
|
|
273
|
+
|
|
206
274
|
/* ---- Data ------------------------------------------------------------ */
|
|
207
275
|
.pire-table-wrap{overflow:auto;background:var(--surface-card);border:1px solid var(--border-subtle);border-radius:var(--radius-2)}
|
|
208
276
|
.pire-table{width:100%;border-collapse:collapse;font:var(--type-body)}
|
|
@@ -348,6 +416,10 @@
|
|
|
348
416
|
|
|
349
417
|
/* ---- Reduced motion --------------------------------------------------- */
|
|
350
418
|
@media (prefers-reduced-motion: reduce){.pire-modal,.pire-toast,.pire-popover,.pire-tooltip,.pire-sidepanel{animation:none}
|
|
419
|
+
/* The skeleton also drops its background sweep, not just the animation: an infinite shimmer is
|
|
420
|
+
exactly the "large, repeated motion" the preference is asking us to stop, and the placeholder
|
|
421
|
+
still reads from its shape and surface contrast alone. */
|
|
422
|
+
.pire-skeleton{animation:none;background-image:none}
|
|
351
423
|
/* !important is the point here: this must beat every component transition, including any a
|
|
352
424
|
consuming app has layered on top. Honouring the OS motion preference outranks the style rule. */
|
|
353
425
|
/* biome-ignore lint/complexity/noImportantStyles: accessibility override must win unconditionally */
|