@xenosystem/elements-react 0.0.1
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/LICENSE +18 -0
- package/README.md +41 -0
- package/dist/XenoElement.d.ts +39 -0
- package/dist/XenoElement.js +25 -0
- package/dist/containers/Avatar.d.ts +42 -0
- package/dist/containers/Avatar.js +29 -0
- package/dist/containers/Badges.d.ts +61 -0
- package/dist/containers/Badges.js +43 -0
- package/dist/containers/Card.d.ts +24 -0
- package/dist/containers/Card.js +5 -0
- package/dist/containers/Chip.d.ts +34 -0
- package/dist/containers/Chip.js +17 -0
- package/dist/containers/DataTable.d.ts +28 -0
- package/dist/containers/DataTable.js +13 -0
- package/dist/containers/ListRow.d.ts +37 -0
- package/dist/containers/ListRow.js +40 -0
- package/dist/containers/MessageBubble.d.ts +33 -0
- package/dist/containers/MessageBubble.js +9 -0
- package/dist/containers/Panel.d.ts +24 -0
- package/dist/containers/Panel.js +6 -0
- package/dist/containers/Table.d.ts +39 -0
- package/dist/containers/Table.js +20 -0
- package/dist/containers/Tile.d.ts +19 -0
- package/dist/containers/Tile.js +7 -0
- package/dist/content/Callout.d.ts +32 -0
- package/dist/content/Callout.js +10 -0
- package/dist/content/Caret.d.ts +9 -0
- package/dist/content/Caret.js +5 -0
- package/dist/content/CodeBlock.d.ts +36 -0
- package/dist/content/CodeBlock.js +50 -0
- package/dist/content/Collapsible.d.ts +24 -0
- package/dist/content/Collapsible.js +19 -0
- package/dist/content/InlineCode.d.ts +12 -0
- package/dist/content/InlineCode.js +5 -0
- package/dist/content/ModelPicker.d.ts +45 -0
- package/dist/content/ModelPicker.js +124 -0
- package/dist/content/SourceCard.d.ts +23 -0
- package/dist/content/SourceCard.js +18 -0
- package/dist/content/SourcesDisclosure.d.ts +26 -0
- package/dist/content/SourcesDisclosure.js +20 -0
- package/dist/controls/Button.d.ts +39 -0
- package/dist/controls/Button.js +16 -0
- package/dist/controls/ConfirmButton.d.ts +22 -0
- package/dist/controls/ConfirmButton.js +22 -0
- package/dist/controls/IconButton.d.ts +45 -0
- package/dist/controls/IconButton.js +15 -0
- package/dist/controls/Switch.d.ts +11 -0
- package/dist/controls/Switch.js +23 -0
- package/dist/controls/TextInput.d.ts +13 -0
- package/dist/controls/TextInput.js +6 -0
- package/dist/controls/Textarea.d.ts +8 -0
- package/dist/controls/Textarea.js +5 -0
- package/dist/controls/ToggleButton.d.ts +23 -0
- package/dist/controls/ToggleButton.js +23 -0
- package/dist/controls/util.d.ts +48 -0
- package/dist/controls/util.js +23 -0
- package/dist/forms/Checkbox.d.ts +18 -0
- package/dist/forms/Checkbox.js +28 -0
- package/dist/forms/RadioGroup.d.ts +51 -0
- package/dist/forms/RadioGroup.js +83 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.js +81 -0
- package/dist/layout/ResizablePanel.d.ts +40 -0
- package/dist/layout/ResizablePanel.js +108 -0
- package/dist/nav/Sidebar.d.ts +60 -0
- package/dist/nav/Sidebar.js +84 -0
- package/dist/overlays/DateTimePicker.d.ts +71 -0
- package/dist/overlays/DateTimePicker.js +191 -0
- package/dist/overlays/Menu.d.ts +28 -0
- package/dist/overlays/Menu.js +32 -0
- package/dist/overlays/MenuItem.d.ts +71 -0
- package/dist/overlays/MenuItem.js +15 -0
- package/dist/overlays/Modal.d.ts +36 -0
- package/dist/overlays/Modal.js +27 -0
- package/dist/overlays/PickerField.d.ts +32 -0
- package/dist/overlays/PickerField.js +40 -0
- package/dist/overlays/PillFilter.d.ts +25 -0
- package/dist/overlays/PillFilter.js +41 -0
- package/dist/overlays/Popover.d.ts +32 -0
- package/dist/overlays/Popover.js +38 -0
- package/dist/overlays/Reveal.d.ts +20 -0
- package/dist/overlays/Reveal.js +45 -0
- package/dist/overlays/SegmentedControl.d.ts +23 -0
- package/dist/overlays/SegmentedControl.js +43 -0
- package/dist/overlays/Tabs.d.ts +32 -0
- package/dist/overlays/Tabs.js +142 -0
- package/dist/overlays/Tooltip.d.ts +23 -0
- package/dist/overlays/Tooltip.js +61 -0
- package/dist/status/ProgressBar.d.ts +20 -0
- package/dist/status/ProgressBar.js +11 -0
- package/dist/status/Spinner.d.ts +16 -0
- package/dist/status/Spinner.js +12 -0
- package/dist/status/StatusIndicator.d.ts +19 -0
- package/dist/status/StatusIndicator.js +11 -0
- package/dist/status/StepTimeline.d.ts +26 -0
- package/dist/status/StepTimeline.js +16 -0
- package/dist/status/ThinkingCube.d.ts +24 -0
- package/dist/status/ThinkingCube.js +11 -0
- package/dist/useDialog.d.ts +45 -0
- package/dist/useDialog.js +122 -0
- package/dist/useGooPill.d.ts +57 -0
- package/dist/useGooPill.js +76 -0
- package/dist/useMenu.d.ts +50 -0
- package/dist/useMenu.js +119 -0
- package/dist/useTabs.d.ts +88 -0
- package/dist/useTabs.js +93 -0
- package/package.json +58 -0
- package/src/chrome-separated.css +132 -0
- package/src/chrome-unified.css +72 -0
- package/src/containers/Badges.css +116 -0
- package/src/containers/avatar.css +70 -0
- package/src/containers/card.css +75 -0
- package/src/containers/chip.css +103 -0
- package/src/containers/list-row.css +132 -0
- package/src/containers/message-bubble.css +112 -0
- package/src/containers/panel.css +120 -0
- package/src/containers/table.css +102 -0
- package/src/containers/tile.css +17 -0
- package/src/content/callout.css +77 -0
- package/src/content/caret.css +30 -0
- package/src/content/code-block.css +159 -0
- package/src/content/collapsible.css +80 -0
- package/src/content/inline-code.css +14 -0
- package/src/content/model-picker.css +219 -0
- package/src/content/source-card.css +99 -0
- package/src/content/sources-disclosure.css +118 -0
- package/src/controls.css +453 -0
- package/src/fonts.css +34 -0
- package/src/forms/Checkbox.css +114 -0
- package/src/forms/RadioGroup.css +115 -0
- package/src/goo.css +97 -0
- package/src/icon-motion.css +1909 -0
- package/src/layout/ResizablePanel.css +79 -0
- package/src/nav/sidebar.css +296 -0
- package/src/overlays/date-time-picker.css +238 -0
- package/src/overlays/menu.css +195 -0
- package/src/overlays/modal.css +167 -0
- package/src/overlays/picker-field.css +81 -0
- package/src/overlays/pill-filter.css +129 -0
- package/src/overlays/reveal.css +56 -0
- package/src/overlays/segmented-control.css +185 -0
- package/src/overlays/tabs.css +143 -0
- package/src/overlays/tooltip.css +126 -0
- package/src/scrollbar.css +184 -0
- package/src/size.css +179 -0
- package/src/status/progress-bar.css +88 -0
- package/src/status/spinner.css +33 -0
- package/src/status/statusindicator.css +65 -0
- package/src/status/steptimeline.css +137 -0
- package/src/status/thinkingcube.css +75 -0
- package/src/xeno-element.css +60 -0
- package/src/xeno-elements.css +71 -0
- package/src/xeno-theme.css +99 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Popover / Menu / MenuItem — a hand-rolled anchored overlay (no portal lib). CSS-first: the panel is an
|
|
3
|
+
* absolutely-positioned card anchored to a `position:relative` wrapper; the anchored edge flips on the
|
|
4
|
+
* `data-align` attribute. Depth is a hairline border + one soft (uncoloured) shadow. Monochrome shell,
|
|
5
|
+
* rounded squares only, tokens from `xeno-theme.css`. Motion is transform/opacity, behind reduced-motion.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* ── Popover (anchor + panel) ─────────────────────────────────────────────────────── */
|
|
9
|
+
.xeno-popover {
|
|
10
|
+
position: relative;
|
|
11
|
+
display: inline-flex;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.xeno-popover-panel {
|
|
15
|
+
position: absolute;
|
|
16
|
+
top: calc(100% + 6px);
|
|
17
|
+
left: 0;
|
|
18
|
+
z-index: 50;
|
|
19
|
+
min-width: 180px;
|
|
20
|
+
padding: 6px;
|
|
21
|
+
background: var(--xeno-elevated);
|
|
22
|
+
border: 1px solid var(--xeno-border);
|
|
23
|
+
border-radius: var(--xeno-radius-md);
|
|
24
|
+
box-shadow:
|
|
25
|
+
0 12px 32px -12px rgba(0, 0, 0, 0.55),
|
|
26
|
+
0 2px 8px rgba(0, 0, 0, 0.24);
|
|
27
|
+
color: var(--xeno-text);
|
|
28
|
+
outline: none;
|
|
29
|
+
}
|
|
30
|
+
.xeno-popover-panel[data-align='end'] {
|
|
31
|
+
left: auto;
|
|
32
|
+
right: 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* ── Menu (a Popover panel that is a role=menu list) ──────────────────────────────── */
|
|
36
|
+
/* `position: relative` + `isolation` come from `xeno-goo-host`, which the component also puts on this
|
|
37
|
+
element; the 4px padding is what `--xeno-goo-inset` defaults to, so the pill lines up with the rows. */
|
|
38
|
+
.xeno-menu {
|
|
39
|
+
min-width: 200px;
|
|
40
|
+
padding: 4px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* The travelling hover pill is a PRIMITIVE, not a menu feature — see `goo.css` and `useGooPill()`. A
|
|
44
|
+
* menu was simply the first list that needed it; a command palette or a product's own hand-rolled
|
|
45
|
+
* dropdown wants the same behaviour, so it does not live here. `<Menu>` carries `xeno-goo-host` and
|
|
46
|
+
* renders the pill as its first child, and the rows below give up painting their own hover background
|
|
47
|
+
* because the pill IS the filled surface. */
|
|
48
|
+
|
|
49
|
+
/* ── MenuItem (row: lead · label · shortcut · chevron) ────────────────────────────── */
|
|
50
|
+
.xeno-menu-item {
|
|
51
|
+
display: flex;
|
|
52
|
+
align-items: center;
|
|
53
|
+
gap: 10px;
|
|
54
|
+
width: 100%;
|
|
55
|
+
/* 32, not the 34 this was written with. The system's own control scale is xs 24 / sm 28 / md 32 /
|
|
56
|
+
lg 36 and there is no 34 in it — the number was picked by eye and never checked against the table
|
|
57
|
+
it belongs to. Found by measuring a product's menu rows against this component: the product was on
|
|
58
|
+
the scale and the library was not. */
|
|
59
|
+
height: 32px;
|
|
60
|
+
margin: 0;
|
|
61
|
+
padding: 0 8px;
|
|
62
|
+
background: transparent;
|
|
63
|
+
border: none;
|
|
64
|
+
/* `md`, not `sm`. The locked radius scale is explicit that overlay chrome is "6px throughout", and a
|
|
65
|
+
menu row is overlay chrome; 5px is the badge/checkbox step. */
|
|
66
|
+
border-radius: var(--xeno-radius-md);
|
|
67
|
+
color: var(--xeno-text);
|
|
68
|
+
font-family: inherit;
|
|
69
|
+
font-size: 14px;
|
|
70
|
+
line-height: 1;
|
|
71
|
+
text-align: left;
|
|
72
|
+
cursor: pointer;
|
|
73
|
+
user-select: none;
|
|
74
|
+
transition:
|
|
75
|
+
background var(--xeno-dur) ease,
|
|
76
|
+
color var(--xeno-dur) ease;
|
|
77
|
+
}
|
|
78
|
+
/* The row paints no background of its own: the travelling pill is the filled surface, and two of them
|
|
79
|
+
would read as two highlights. */
|
|
80
|
+
.xeno-menu-item:hover:not(:disabled),
|
|
81
|
+
.xeno-menu-item:focus-visible {
|
|
82
|
+
outline: none;
|
|
83
|
+
}
|
|
84
|
+
.xeno-menu-item:focus-visible {
|
|
85
|
+
box-shadow: inset 0 0 0 1px var(--xeno-border);
|
|
86
|
+
}
|
|
87
|
+
.xeno-menu-item[data-availability='disabled'] {
|
|
88
|
+
opacity: 0.4;
|
|
89
|
+
cursor: not-allowed;
|
|
90
|
+
}
|
|
91
|
+
.xeno-menu-item[data-variant='danger'] {
|
|
92
|
+
color: var(--xeno-danger);
|
|
93
|
+
}
|
|
94
|
+
.xeno-menu-item[data-variant='danger']:hover:not(:disabled),
|
|
95
|
+
.xeno-menu-item[data-variant='danger']:focus-visible {
|
|
96
|
+
color: var(--xeno-danger-hover);
|
|
97
|
+
background: var(--xeno-hover);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Leading slot — reserved icon/check column (a rounded box, never a circle). */
|
|
101
|
+
.xeno-menu-item-lead {
|
|
102
|
+
flex: none;
|
|
103
|
+
display: inline-flex;
|
|
104
|
+
align-items: center;
|
|
105
|
+
justify-content: center;
|
|
106
|
+
width: 16px;
|
|
107
|
+
height: 16px;
|
|
108
|
+
color: var(--xeno-muted);
|
|
109
|
+
}
|
|
110
|
+
.xeno-menu-item[data-selection='on'] .xeno-menu-item-lead {
|
|
111
|
+
color: var(--xeno-text);
|
|
112
|
+
}
|
|
113
|
+
.xeno-menu-item[data-variant='danger'] .xeno-menu-item-lead {
|
|
114
|
+
color: inherit;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* Label — grows and truncates. */
|
|
118
|
+
.xeno-menu-item-label {
|
|
119
|
+
flex: 1 1 auto;
|
|
120
|
+
min-width: 0;
|
|
121
|
+
overflow: hidden;
|
|
122
|
+
white-space: nowrap;
|
|
123
|
+
text-overflow: ellipsis;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* Shortcut — trailing, muted mono. */
|
|
127
|
+
.xeno-menu-item-shortcut {
|
|
128
|
+
flex: none;
|
|
129
|
+
margin-left: 12px;
|
|
130
|
+
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
|
|
131
|
+
font-size: 12px;
|
|
132
|
+
letter-spacing: 0.02em;
|
|
133
|
+
color: var(--xeno-muted);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* Value — trailing, muted, but in the UI font, and that is the whole point of it being a separate
|
|
137
|
+
slot. A shortcut is a KEY: mono, tracked out, because it stands for something the reader will
|
|
138
|
+
press. A value is a WORD the reader chose ("All", "Last activity") and setting a word in mono
|
|
139
|
+
makes it read as a key too. Same slot, same muted colour, different typeface — the distinction
|
|
140
|
+
the shortcut rule alone could not draw. Sits before the chevron, so a submenu row reads
|
|
141
|
+
label → current value → "there is more this way". */
|
|
142
|
+
.xeno-menu-item-value {
|
|
143
|
+
flex: none;
|
|
144
|
+
/* 2, not the shortcut's 12 — the row's own 10px `gap` is already between them, and a value is a
|
|
145
|
+
word rather than a key glyph, so it needs the label's width more than it needs the air. At 12 a
|
|
146
|
+
"Last activity / All ›" row ran out of room and ellipsed its own label. */
|
|
147
|
+
margin-left: 2px;
|
|
148
|
+
color: var(--xeno-muted);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* Submenu chevron — trailing, muted. */
|
|
152
|
+
.xeno-menu-item-chevron {
|
|
153
|
+
flex: none;
|
|
154
|
+
display: inline-flex;
|
|
155
|
+
align-items: center;
|
|
156
|
+
margin-left: 2px;
|
|
157
|
+
color: var(--xeno-muted);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* A DISCLOSURE row turns the same chevron a quarter — the region it opens is below, not beside, and
|
|
161
|
+
the arrow should point at it. It turns rather than swapping to a chevron-down so the reader can
|
|
162
|
+
see WHICH row moved; the rotation is the state change, not a redraw. */
|
|
163
|
+
.xeno-menu-item-chevron[data-state] {
|
|
164
|
+
transition: transform 0.2s cubic-bezier(0.22, 1, 0.36, 1);
|
|
165
|
+
}
|
|
166
|
+
.xeno-menu-item-chevron[data-state='open'] {
|
|
167
|
+
transform: rotate(90deg);
|
|
168
|
+
}
|
|
169
|
+
@media (prefers-reduced-motion: reduce) {
|
|
170
|
+
.xeno-menu-item-chevron[data-state] {
|
|
171
|
+
transition: none;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* ── Motion (transform/opacity only; opt-in) ─────────────────────────────────────── */
|
|
176
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
177
|
+
.xeno-popover-panel {
|
|
178
|
+
animation: xeno-popover-in var(--xeno-dur) var(--xeno-ease);
|
|
179
|
+
transform-origin: top left;
|
|
180
|
+
}
|
|
181
|
+
.xeno-popover-panel[data-align='end'] {
|
|
182
|
+
transform-origin: top right;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@keyframes xeno-popover-in {
|
|
187
|
+
from {
|
|
188
|
+
opacity: 0;
|
|
189
|
+
transform: translateY(-4px) scale(0.98);
|
|
190
|
+
}
|
|
191
|
+
to {
|
|
192
|
+
opacity: 1;
|
|
193
|
+
transform: none;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Modal / Sheet — the locked floating-window overlay. CSS-first: the placement is driven by the
|
|
3
|
+
* `data-variant` attribute on the scrim and the card, everything else is tokens from `xeno-theme.css`.
|
|
4
|
+
* Depth is a hairline border + one soft (uncoloured) shadow; the scrim tints from `--xeno-canvas` so it
|
|
5
|
+
* stays monochrome in both themes. No literal colours, no coloured glow.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* ── Scrim / overlay ─────────────────────────────────────────────────────────────── */
|
|
9
|
+
.xeno-modal-overlay {
|
|
10
|
+
position: fixed;
|
|
11
|
+
inset: 0;
|
|
12
|
+
z-index: 1000;
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
justify-content: center;
|
|
16
|
+
padding: 24px;
|
|
17
|
+
background: color-mix(in srgb, var(--xeno-canvas) 72%, transparent);
|
|
18
|
+
-webkit-backdrop-filter: blur(4px);
|
|
19
|
+
backdrop-filter: blur(4px);
|
|
20
|
+
}
|
|
21
|
+
.xeno-modal-overlay[data-variant='sheet'] {
|
|
22
|
+
align-items: flex-end;
|
|
23
|
+
padding: 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* ── Card ────────────────────────────────────────────────────────────────────────── */
|
|
27
|
+
/*
|
|
28
|
+
* The card is a chrome stack, like a panel — §"Panel Structure"'s construction, floated. Every value
|
|
29
|
+
* comes from `--xeno-chrome-*`, so the same DOM renders as a shell carrying three plates with 2px
|
|
30
|
+
* gaps (`separated`) or as one clipped card with hairline dividers and a soft shadow (`unified`).
|
|
31
|
+
*/
|
|
32
|
+
.xeno-modal {
|
|
33
|
+
display: flex;
|
|
34
|
+
flex-direction: column;
|
|
35
|
+
gap: var(--xeno-chrome-gap);
|
|
36
|
+
padding: var(--xeno-chrome-inset);
|
|
37
|
+
width: min(92vw, 480px);
|
|
38
|
+
max-height: min(85vh, 640px);
|
|
39
|
+
background: var(--xeno-chrome-shell);
|
|
40
|
+
border: 1px solid var(--xeno-chrome-border);
|
|
41
|
+
border-radius: var(--xeno-chrome-radius);
|
|
42
|
+
box-shadow: var(--xeno-chrome-shadow);
|
|
43
|
+
outline: none;
|
|
44
|
+
overflow: hidden;
|
|
45
|
+
}
|
|
46
|
+
.xeno-modal[data-variant='sheet'] {
|
|
47
|
+
width: 100%;
|
|
48
|
+
max-width: 640px;
|
|
49
|
+
max-height: 85vh;
|
|
50
|
+
border-bottom: none;
|
|
51
|
+
/* radius only on the top corners — it meets the bottom edge */
|
|
52
|
+
border-radius: var(--xeno-chrome-radius) var(--xeno-chrome-radius) 0 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* ── Header (title + close) ──────────────────────────────────────────────────────── */
|
|
56
|
+
.xeno-modal-header {
|
|
57
|
+
flex: 0 0 auto;
|
|
58
|
+
display: flex;
|
|
59
|
+
align-items: center;
|
|
60
|
+
gap: 8px;
|
|
61
|
+
padding: var(--xeno-chrome-dialog-header-pad);
|
|
62
|
+
background: var(--xeno-chrome-plate);
|
|
63
|
+
-webkit-backdrop-filter: var(--xeno-chrome-plate-filter);
|
|
64
|
+
backdrop-filter: var(--xeno-chrome-plate-filter);
|
|
65
|
+
border-bottom: var(--xeno-chrome-divider);
|
|
66
|
+
}
|
|
67
|
+
.xeno-modal-title {
|
|
68
|
+
min-width: 0;
|
|
69
|
+
margin: 0;
|
|
70
|
+
font-size: var(--xeno-chrome-dialog-title-size);
|
|
71
|
+
font-weight: var(--xeno-chrome-dialog-title-weight);
|
|
72
|
+
letter-spacing: 0.03em;
|
|
73
|
+
line-height: 1.3;
|
|
74
|
+
color: var(--xeno-text);
|
|
75
|
+
overflow: hidden;
|
|
76
|
+
text-overflow: ellipsis;
|
|
77
|
+
white-space: nowrap;
|
|
78
|
+
}
|
|
79
|
+
/* Pushes the close button to the trailing edge, with or without a title. */
|
|
80
|
+
.xeno-modal-close {
|
|
81
|
+
flex: 0 0 auto;
|
|
82
|
+
margin-inline-start: auto;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* ── Body (the one scroll region) ────────────────────────────────────────────────── */
|
|
86
|
+
.xeno-modal-body {
|
|
87
|
+
flex: 1 1 auto;
|
|
88
|
+
min-height: 0;
|
|
89
|
+
overflow-y: auto;
|
|
90
|
+
padding: var(--xeno-chrome-dialog-body-pad);
|
|
91
|
+
background: var(--xeno-chrome-body);
|
|
92
|
+
color: var(--xeno-text);
|
|
93
|
+
font-size: var(--xeno-chrome-dialog-body-size);
|
|
94
|
+
line-height: 1.55;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* ── Footer (right-aligned actions) ──────────────────────────────────────────────── */
|
|
98
|
+
.xeno-modal-footer {
|
|
99
|
+
flex: 0 0 auto;
|
|
100
|
+
display: flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
justify-content: flex-end;
|
|
103
|
+
gap: var(--xeno-chrome-dialog-footer-gap);
|
|
104
|
+
padding: var(--xeno-chrome-dialog-footer-pad);
|
|
105
|
+
background: var(--xeno-chrome-plate);
|
|
106
|
+
border-top: var(--xeno-chrome-divider);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* ── Motion (transform/opacity only; opt-in) ─────────────────────────────────────── */
|
|
110
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
111
|
+
.xeno-modal-overlay {
|
|
112
|
+
animation: xeno-modal-scrim-in var(--xeno-dur) var(--xeno-ease);
|
|
113
|
+
}
|
|
114
|
+
.xeno-modal {
|
|
115
|
+
animation: xeno-modal-pop-in calc(var(--xeno-dur) * 1.4) var(--xeno-ease);
|
|
116
|
+
}
|
|
117
|
+
.xeno-modal[data-variant='sheet'] {
|
|
118
|
+
animation: xeno-modal-sheet-in calc(var(--xeno-dur) * 1.7) var(--xeno-ease);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@keyframes xeno-modal-scrim-in {
|
|
123
|
+
from {
|
|
124
|
+
opacity: 0;
|
|
125
|
+
}
|
|
126
|
+
to {
|
|
127
|
+
opacity: 1;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
@keyframes xeno-modal-pop-in {
|
|
131
|
+
from {
|
|
132
|
+
opacity: 0;
|
|
133
|
+
transform: translateY(8px) scale(0.985);
|
|
134
|
+
}
|
|
135
|
+
to {
|
|
136
|
+
opacity: 1;
|
|
137
|
+
transform: none;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
@keyframes xeno-modal-sheet-in {
|
|
141
|
+
from {
|
|
142
|
+
transform: translateY(100%);
|
|
143
|
+
}
|
|
144
|
+
to {
|
|
145
|
+
transform: translateY(0);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/*
|
|
150
|
+
* §4 "Radius Is Per-Corner, Not Per-Element": a corner is rounded only where the surface meets
|
|
151
|
+
* NOTHING. Each plate faces its neighbour across the 2px gap, so the facing corners are SQUARE — a
|
|
152
|
+
* uniform radius on every plate is the tell that this section was skipped, and it was.
|
|
153
|
+
*
|
|
154
|
+
* Declared on first/last child rather than per plate so a stack with no header, or no footer, still
|
|
155
|
+
* presents a rounded outer edge instead of a square one against the rounded shell.
|
|
156
|
+
*/
|
|
157
|
+
.xeno-modal > * {
|
|
158
|
+
border-radius: 0;
|
|
159
|
+
}
|
|
160
|
+
.xeno-modal > :first-child {
|
|
161
|
+
border-top-left-radius: var(--xeno-chrome-radius-plate);
|
|
162
|
+
border-top-right-radius: var(--xeno-chrome-radius-plate);
|
|
163
|
+
}
|
|
164
|
+
.xeno-modal > :last-child {
|
|
165
|
+
border-bottom-left-radius: var(--xeno-chrome-radius-plate);
|
|
166
|
+
border-bottom-right-radius: var(--xeno-chrome-radius-plate);
|
|
167
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* PickerField — a form field that reveals a panel with the chat's "Scheduled" open/close animation:
|
|
3
|
+
* the panel slides DOWN from behind the field (clipped by the popover's overflow) while its content
|
|
4
|
+
* fades up a beat later, so nothing pops. Reverses on close. Monochrome, tokens only, motion is
|
|
5
|
+
* transform/opacity behind the reduce guard.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
.xeno-field {
|
|
9
|
+
position: relative;
|
|
10
|
+
display: inline-block;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* ── Trigger (the field itself) ──────────────────────────────────────────────────── */
|
|
14
|
+
.xeno-field-trigger {
|
|
15
|
+
display: inline-flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
gap: 8px;
|
|
18
|
+
min-width: 156px;
|
|
19
|
+
height: 34px;
|
|
20
|
+
padding: 0 10px;
|
|
21
|
+
font-family: inherit;
|
|
22
|
+
font-size: 13px;
|
|
23
|
+
color: var(--xeno-text);
|
|
24
|
+
background: var(--xeno-canvas);
|
|
25
|
+
border: 1px solid var(--xeno-border);
|
|
26
|
+
border-radius: var(--xeno-radius-md);
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
transition:
|
|
29
|
+
border-color var(--xeno-dur) ease,
|
|
30
|
+
background var(--xeno-dur) ease;
|
|
31
|
+
}
|
|
32
|
+
.xeno-field-trigger:hover {
|
|
33
|
+
border-color: var(--xeno-muted);
|
|
34
|
+
}
|
|
35
|
+
.xeno-field-trigger[data-open='true'] {
|
|
36
|
+
border-color: var(--xeno-muted);
|
|
37
|
+
background: var(--xeno-hover);
|
|
38
|
+
}
|
|
39
|
+
.xeno-field-trigger:focus-visible {
|
|
40
|
+
outline: none;
|
|
41
|
+
box-shadow:
|
|
42
|
+
0 0 0 2px var(--xeno-canvas),
|
|
43
|
+
0 0 0 4px var(--xeno-border);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.xeno-field-icon {
|
|
47
|
+
display: inline-flex;
|
|
48
|
+
color: var(--xeno-muted);
|
|
49
|
+
}
|
|
50
|
+
.xeno-field-label {
|
|
51
|
+
color: var(--xeno-muted);
|
|
52
|
+
font-size: 12px;
|
|
53
|
+
}
|
|
54
|
+
.xeno-field-value {
|
|
55
|
+
flex: 1 1 auto;
|
|
56
|
+
min-width: 0;
|
|
57
|
+
text-align: left;
|
|
58
|
+
white-space: nowrap;
|
|
59
|
+
overflow: hidden;
|
|
60
|
+
text-overflow: ellipsis;
|
|
61
|
+
font-variant-numeric: tabular-nums;
|
|
62
|
+
}
|
|
63
|
+
.xeno-field-value[data-placeholder='true'] {
|
|
64
|
+
color: var(--xeno-muted);
|
|
65
|
+
}
|
|
66
|
+
.xeno-field-chevron {
|
|
67
|
+
display: inline-flex;
|
|
68
|
+
color: var(--xeno-muted);
|
|
69
|
+
transform: rotate(90deg); /* chevron-right → points down when closed */
|
|
70
|
+
transition: transform var(--xeno-dur) ease;
|
|
71
|
+
}
|
|
72
|
+
.xeno-field-trigger[data-open='true'] .xeno-field-chevron {
|
|
73
|
+
transform: rotate(-90deg); /* points up when open */
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* The reveal itself (the unfolding panel) lives in reveal.css — PickerField composes <Reveal>. */
|
|
77
|
+
@media (prefers-reduced-motion: reduce) {
|
|
78
|
+
.xeno-field-chevron {
|
|
79
|
+
transition: none;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* <PillFilter> — a wrapping row of small toggle pills (rounded squares) for multi-select. CSS-first:
|
|
3
|
+
* each pill carries `data-selection` (on/off) and `data-availability`; a selected pill fills to
|
|
4
|
+
* `var(--xeno-control)` with a `var(--xeno-muted)` hairline. Size metrics arrive as inline vars.
|
|
5
|
+
* Monochrome only.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
.xeno-pillfilter {
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-wrap: wrap;
|
|
11
|
+
align-items: center;
|
|
12
|
+
gap: 8px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* The "on" fill is a LAYER that floods in from the leading edge, not a background that switches — the
|
|
16
|
+
same gesture <ToggleButton> makes, because these are the same thing: a button that stays pressed.
|
|
17
|
+
Clipped, never scaled, so the pill itself never changes size. */
|
|
18
|
+
.xeno-pill {
|
|
19
|
+
--xeno-pill-goo-dur: 300ms;
|
|
20
|
+
--xeno-pill-goo-ease: var(--xeno-ease, cubic-bezier(0.22, 1, 0.36, 1));
|
|
21
|
+
position: relative;
|
|
22
|
+
isolation: isolate;
|
|
23
|
+
display: inline-flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
gap: 6px;
|
|
26
|
+
height: var(--xeno-h, 28px);
|
|
27
|
+
padding: 0 var(--xeno-padx, 10px);
|
|
28
|
+
font-family: inherit;
|
|
29
|
+
font-size: var(--xeno-font, 13px);
|
|
30
|
+
font-weight: 500;
|
|
31
|
+
line-height: 1;
|
|
32
|
+
white-space: nowrap;
|
|
33
|
+
color: var(--xeno-muted);
|
|
34
|
+
background: var(--xeno-surface);
|
|
35
|
+
border: 1px solid var(--xeno-border);
|
|
36
|
+
border-radius: var(--xeno-radius-md);
|
|
37
|
+
cursor: pointer;
|
|
38
|
+
user-select: none;
|
|
39
|
+
transition:
|
|
40
|
+
color var(--xeno-dur) var(--xeno-ease),
|
|
41
|
+
background var(--xeno-dur) var(--xeno-ease),
|
|
42
|
+
border-color var(--xeno-dur) var(--xeno-ease);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.xeno-pill::before {
|
|
46
|
+
content: '';
|
|
47
|
+
position: absolute;
|
|
48
|
+
z-index: -1;
|
|
49
|
+
/* -1px so the layer reaches under the border, exactly where a background would have painted. */
|
|
50
|
+
inset: -1px;
|
|
51
|
+
border-radius: inherit;
|
|
52
|
+
background: var(--xeno-control);
|
|
53
|
+
clip-path: circle(0% at var(--xeno-padx, 10px) 50%);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Hover reads in BOTH states: the fill covers any background tint once the pill is on, so the border
|
|
57
|
+
carries the affordance — it is visible either way. (It also settles an ordering bug: the hover rule
|
|
58
|
+
out-specified the selected rule, so hovering a selected pill used to swap `--xeno-control` for a
|
|
59
|
+
translucent tint and the pill appeared to un-fill.) */
|
|
60
|
+
.xeno-pill:hover:not([data-availability='disabled']) {
|
|
61
|
+
color: var(--xeno-text);
|
|
62
|
+
background: var(--xeno-hover);
|
|
63
|
+
border-color: var(--xeno-muted);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.xeno-pill[data-selection='on'] {
|
|
67
|
+
color: var(--xeno-text);
|
|
68
|
+
border-color: var(--xeno-muted);
|
|
69
|
+
}
|
|
70
|
+
.xeno-pill[data-selection='on']::before {
|
|
71
|
+
clip-path: circle(150% at var(--xeno-padx, 10px) 50%);
|
|
72
|
+
}
|
|
73
|
+
/* Gated per pill on `data-motion="ready"`, set only once that pill's own selection has changed. */
|
|
74
|
+
.xeno-pill[data-motion='ready'][data-selection='on']::before {
|
|
75
|
+
animation: xeno-pill-goo-in var(--xeno-pill-goo-dur) var(--xeno-pill-goo-ease);
|
|
76
|
+
}
|
|
77
|
+
.xeno-pill[data-motion='ready'][data-selection='off']::before {
|
|
78
|
+
animation: xeno-pill-goo-out var(--xeno-pill-goo-dur) var(--xeno-pill-goo-ease);
|
|
79
|
+
}
|
|
80
|
+
@keyframes xeno-pill-goo-in {
|
|
81
|
+
from { clip-path: circle(0% at var(--xeno-padx, 10px) 50%); }
|
|
82
|
+
to { clip-path: circle(150% at var(--xeno-padx, 10px) 50%); }
|
|
83
|
+
}
|
|
84
|
+
@keyframes xeno-pill-goo-out {
|
|
85
|
+
from { clip-path: circle(150% at var(--xeno-padx, 10px) 50%); }
|
|
86
|
+
to { clip-path: circle(0% at var(--xeno-padx, 10px) 50%); }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.xeno-pill:focus-visible {
|
|
90
|
+
outline: none;
|
|
91
|
+
box-shadow:
|
|
92
|
+
0 0 0 2px var(--xeno-canvas),
|
|
93
|
+
0 0 0 4px var(--xeno-border);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.xeno-pill[data-availability='disabled'] {
|
|
97
|
+
opacity: 0.4;
|
|
98
|
+
cursor: not-allowed;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Count badge — a small inset rounded square, never a circle. */
|
|
102
|
+
.xeno-pill-count {
|
|
103
|
+
display: inline-flex;
|
|
104
|
+
align-items: center;
|
|
105
|
+
justify-content: center;
|
|
106
|
+
min-width: 18px;
|
|
107
|
+
height: 18px;
|
|
108
|
+
padding: 0 5px;
|
|
109
|
+
font-size: 11px;
|
|
110
|
+
font-weight: 600;
|
|
111
|
+
line-height: 1;
|
|
112
|
+
color: var(--xeno-muted);
|
|
113
|
+
background: var(--xeno-control);
|
|
114
|
+
border-radius: var(--xeno-radius-xs);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.xeno-pill[data-selection='on'] .xeno-pill-count {
|
|
118
|
+
color: var(--xeno-text);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@media (prefers-reduced-motion: reduce) {
|
|
122
|
+
.xeno-pill {
|
|
123
|
+
transition: none;
|
|
124
|
+
}
|
|
125
|
+
/* The fill still marks the pill — it just arrives instead of pouring. */
|
|
126
|
+
.xeno-pill::before {
|
|
127
|
+
animation: none !important;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Reveal — an in-flow animated disclosure. The region's height (inline, animated) pushes following
|
|
3
|
+
* content DOWN so the panel never floats over the page; the clip is panel-WIDTH so a wide child shows
|
|
4
|
+
* full width and only unfolds VERTICALLY; the content fades up a beat after. Tokens only, motion is
|
|
5
|
+
* transform/opacity/height behind the reduce guard.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
.xeno-reveal {
|
|
9
|
+
position: relative;
|
|
10
|
+
height: 0;
|
|
11
|
+
margin-top: 7px;
|
|
12
|
+
transition: height 240ms var(--xeno-ease);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* Absolute + panel-width (max-content), so a wide calendar shows FULL width (never cut on the side);
|
|
16
|
+
as tall as the region (height:100%), so the reveal only clips VERTICALLY. `data-clip=false` lifts it
|
|
17
|
+
once open so edge focus rings are not cut. */
|
|
18
|
+
.xeno-reveal-clip {
|
|
19
|
+
position: absolute;
|
|
20
|
+
top: 0;
|
|
21
|
+
left: 0;
|
|
22
|
+
width: max-content;
|
|
23
|
+
height: 100%;
|
|
24
|
+
overflow: hidden;
|
|
25
|
+
z-index: 50;
|
|
26
|
+
}
|
|
27
|
+
.xeno-reveal-clip[data-align='end'] {
|
|
28
|
+
left: auto;
|
|
29
|
+
right: 0;
|
|
30
|
+
}
|
|
31
|
+
.xeno-reveal-clip[data-clip='false'] {
|
|
32
|
+
overflow: visible;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.xeno-reveal-content {
|
|
36
|
+
opacity: 0;
|
|
37
|
+
transform: translateY(8px);
|
|
38
|
+
transition:
|
|
39
|
+
opacity 200ms ease-out 40ms,
|
|
40
|
+
transform 200ms ease-out 40ms;
|
|
41
|
+
}
|
|
42
|
+
.xeno-reveal[data-open='true'] .xeno-reveal-content {
|
|
43
|
+
opacity: 1;
|
|
44
|
+
transform: translateY(0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@media (prefers-reduced-motion: reduce) {
|
|
48
|
+
.xeno-reveal,
|
|
49
|
+
.xeno-reveal-content {
|
|
50
|
+
transition: none;
|
|
51
|
+
}
|
|
52
|
+
.xeno-reveal-content {
|
|
53
|
+
opacity: 1;
|
|
54
|
+
transform: none;
|
|
55
|
+
}
|
|
56
|
+
}
|