@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.
Files changed (153) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +41 -0
  3. package/dist/XenoElement.d.ts +39 -0
  4. package/dist/XenoElement.js +25 -0
  5. package/dist/containers/Avatar.d.ts +42 -0
  6. package/dist/containers/Avatar.js +29 -0
  7. package/dist/containers/Badges.d.ts +61 -0
  8. package/dist/containers/Badges.js +43 -0
  9. package/dist/containers/Card.d.ts +24 -0
  10. package/dist/containers/Card.js +5 -0
  11. package/dist/containers/Chip.d.ts +34 -0
  12. package/dist/containers/Chip.js +17 -0
  13. package/dist/containers/DataTable.d.ts +28 -0
  14. package/dist/containers/DataTable.js +13 -0
  15. package/dist/containers/ListRow.d.ts +37 -0
  16. package/dist/containers/ListRow.js +40 -0
  17. package/dist/containers/MessageBubble.d.ts +33 -0
  18. package/dist/containers/MessageBubble.js +9 -0
  19. package/dist/containers/Panel.d.ts +24 -0
  20. package/dist/containers/Panel.js +6 -0
  21. package/dist/containers/Table.d.ts +39 -0
  22. package/dist/containers/Table.js +20 -0
  23. package/dist/containers/Tile.d.ts +19 -0
  24. package/dist/containers/Tile.js +7 -0
  25. package/dist/content/Callout.d.ts +32 -0
  26. package/dist/content/Callout.js +10 -0
  27. package/dist/content/Caret.d.ts +9 -0
  28. package/dist/content/Caret.js +5 -0
  29. package/dist/content/CodeBlock.d.ts +36 -0
  30. package/dist/content/CodeBlock.js +50 -0
  31. package/dist/content/Collapsible.d.ts +24 -0
  32. package/dist/content/Collapsible.js +19 -0
  33. package/dist/content/InlineCode.d.ts +12 -0
  34. package/dist/content/InlineCode.js +5 -0
  35. package/dist/content/ModelPicker.d.ts +45 -0
  36. package/dist/content/ModelPicker.js +124 -0
  37. package/dist/content/SourceCard.d.ts +23 -0
  38. package/dist/content/SourceCard.js +18 -0
  39. package/dist/content/SourcesDisclosure.d.ts +26 -0
  40. package/dist/content/SourcesDisclosure.js +20 -0
  41. package/dist/controls/Button.d.ts +39 -0
  42. package/dist/controls/Button.js +16 -0
  43. package/dist/controls/ConfirmButton.d.ts +22 -0
  44. package/dist/controls/ConfirmButton.js +22 -0
  45. package/dist/controls/IconButton.d.ts +45 -0
  46. package/dist/controls/IconButton.js +15 -0
  47. package/dist/controls/Switch.d.ts +11 -0
  48. package/dist/controls/Switch.js +23 -0
  49. package/dist/controls/TextInput.d.ts +13 -0
  50. package/dist/controls/TextInput.js +6 -0
  51. package/dist/controls/Textarea.d.ts +8 -0
  52. package/dist/controls/Textarea.js +5 -0
  53. package/dist/controls/ToggleButton.d.ts +23 -0
  54. package/dist/controls/ToggleButton.js +23 -0
  55. package/dist/controls/util.d.ts +48 -0
  56. package/dist/controls/util.js +23 -0
  57. package/dist/forms/Checkbox.d.ts +18 -0
  58. package/dist/forms/Checkbox.js +28 -0
  59. package/dist/forms/RadioGroup.d.ts +51 -0
  60. package/dist/forms/RadioGroup.js +83 -0
  61. package/dist/index.d.ts +75 -0
  62. package/dist/index.js +81 -0
  63. package/dist/layout/ResizablePanel.d.ts +40 -0
  64. package/dist/layout/ResizablePanel.js +108 -0
  65. package/dist/nav/Sidebar.d.ts +60 -0
  66. package/dist/nav/Sidebar.js +84 -0
  67. package/dist/overlays/DateTimePicker.d.ts +71 -0
  68. package/dist/overlays/DateTimePicker.js +191 -0
  69. package/dist/overlays/Menu.d.ts +28 -0
  70. package/dist/overlays/Menu.js +32 -0
  71. package/dist/overlays/MenuItem.d.ts +71 -0
  72. package/dist/overlays/MenuItem.js +15 -0
  73. package/dist/overlays/Modal.d.ts +36 -0
  74. package/dist/overlays/Modal.js +27 -0
  75. package/dist/overlays/PickerField.d.ts +32 -0
  76. package/dist/overlays/PickerField.js +40 -0
  77. package/dist/overlays/PillFilter.d.ts +25 -0
  78. package/dist/overlays/PillFilter.js +41 -0
  79. package/dist/overlays/Popover.d.ts +32 -0
  80. package/dist/overlays/Popover.js +38 -0
  81. package/dist/overlays/Reveal.d.ts +20 -0
  82. package/dist/overlays/Reveal.js +45 -0
  83. package/dist/overlays/SegmentedControl.d.ts +23 -0
  84. package/dist/overlays/SegmentedControl.js +43 -0
  85. package/dist/overlays/Tabs.d.ts +32 -0
  86. package/dist/overlays/Tabs.js +142 -0
  87. package/dist/overlays/Tooltip.d.ts +23 -0
  88. package/dist/overlays/Tooltip.js +61 -0
  89. package/dist/status/ProgressBar.d.ts +20 -0
  90. package/dist/status/ProgressBar.js +11 -0
  91. package/dist/status/Spinner.d.ts +16 -0
  92. package/dist/status/Spinner.js +12 -0
  93. package/dist/status/StatusIndicator.d.ts +19 -0
  94. package/dist/status/StatusIndicator.js +11 -0
  95. package/dist/status/StepTimeline.d.ts +26 -0
  96. package/dist/status/StepTimeline.js +16 -0
  97. package/dist/status/ThinkingCube.d.ts +24 -0
  98. package/dist/status/ThinkingCube.js +11 -0
  99. package/dist/useDialog.d.ts +45 -0
  100. package/dist/useDialog.js +122 -0
  101. package/dist/useGooPill.d.ts +57 -0
  102. package/dist/useGooPill.js +76 -0
  103. package/dist/useMenu.d.ts +50 -0
  104. package/dist/useMenu.js +119 -0
  105. package/dist/useTabs.d.ts +88 -0
  106. package/dist/useTabs.js +93 -0
  107. package/package.json +58 -0
  108. package/src/chrome-separated.css +132 -0
  109. package/src/chrome-unified.css +72 -0
  110. package/src/containers/Badges.css +116 -0
  111. package/src/containers/avatar.css +70 -0
  112. package/src/containers/card.css +75 -0
  113. package/src/containers/chip.css +103 -0
  114. package/src/containers/list-row.css +132 -0
  115. package/src/containers/message-bubble.css +112 -0
  116. package/src/containers/panel.css +120 -0
  117. package/src/containers/table.css +102 -0
  118. package/src/containers/tile.css +17 -0
  119. package/src/content/callout.css +77 -0
  120. package/src/content/caret.css +30 -0
  121. package/src/content/code-block.css +159 -0
  122. package/src/content/collapsible.css +80 -0
  123. package/src/content/inline-code.css +14 -0
  124. package/src/content/model-picker.css +219 -0
  125. package/src/content/source-card.css +99 -0
  126. package/src/content/sources-disclosure.css +118 -0
  127. package/src/controls.css +453 -0
  128. package/src/fonts.css +34 -0
  129. package/src/forms/Checkbox.css +114 -0
  130. package/src/forms/RadioGroup.css +115 -0
  131. package/src/goo.css +97 -0
  132. package/src/icon-motion.css +1909 -0
  133. package/src/layout/ResizablePanel.css +79 -0
  134. package/src/nav/sidebar.css +296 -0
  135. package/src/overlays/date-time-picker.css +238 -0
  136. package/src/overlays/menu.css +195 -0
  137. package/src/overlays/modal.css +167 -0
  138. package/src/overlays/picker-field.css +81 -0
  139. package/src/overlays/pill-filter.css +129 -0
  140. package/src/overlays/reveal.css +56 -0
  141. package/src/overlays/segmented-control.css +185 -0
  142. package/src/overlays/tabs.css +143 -0
  143. package/src/overlays/tooltip.css +126 -0
  144. package/src/scrollbar.css +184 -0
  145. package/src/size.css +179 -0
  146. package/src/status/progress-bar.css +88 -0
  147. package/src/status/spinner.css +33 -0
  148. package/src/status/statusindicator.css +65 -0
  149. package/src/status/steptimeline.css +137 -0
  150. package/src/status/thinkingcube.css +75 -0
  151. package/src/xeno-element.css +60 -0
  152. package/src/xeno-elements.css +71 -0
  153. package/src/xeno-theme.css +99 -0
@@ -0,0 +1,79 @@
1
+ /*
2
+ * ResizablePanel — a fixed-width panel + a 4px draggable divider. CSS-first: `data-side` places the
3
+ * handle, `data-dragging` locks the grab cursor + text selection during a drag, `data-availability`
4
+ * carries the disabled axis. Monochrome shell: the handle rests on the hairline `--xeno-border` and
5
+ * lifts to `--xeno-muted` on hover/focus. No literal colours, no hue.
6
+ */
7
+
8
+ .xeno-resizable {
9
+ display: flex;
10
+ align-items: stretch;
11
+ min-height: 0;
12
+ }
13
+
14
+ /* While dragging, the grab cursor should persist even as the pointer leaves the 4px handle. */
15
+ .xeno-resizable[data-dragging='true'] {
16
+ cursor: col-resize;
17
+ user-select: none;
18
+ }
19
+
20
+ /* The sized child — width is the only driven property; contents scroll within. */
21
+ .xeno-resizable-panel {
22
+ flex: 0 0 auto;
23
+ min-width: 0;
24
+ min-height: 0;
25
+ overflow: auto;
26
+ }
27
+
28
+ /* The divider: a hairline strip that lifts on hover/focus. Rounded-square grammar (no circles). */
29
+ .xeno-resizable-handle {
30
+ position: relative;
31
+ flex: 0 0 auto;
32
+ align-self: stretch;
33
+ width: 4px;
34
+ padding: 0;
35
+ border: none;
36
+ border-radius: var(--xeno-radius-hair);
37
+ background: var(--xeno-border);
38
+ cursor: col-resize;
39
+ touch-action: none;
40
+ transition: background var(--xeno-dur) var(--xeno-ease);
41
+ }
42
+
43
+ /* Widen the pointer/press target beyond the 4px visual without shifting layout. */
44
+ .xeno-resizable-handle::before {
45
+ content: '';
46
+ position: absolute;
47
+ inset: 0 -4px;
48
+ }
49
+
50
+ .xeno-resizable-handle:hover {
51
+ background: var(--xeno-muted);
52
+ }
53
+
54
+ .xeno-resizable-handle:focus-visible {
55
+ outline: none;
56
+ background: var(--xeno-muted);
57
+ box-shadow:
58
+ 0 0 0 2px var(--xeno-canvas),
59
+ 0 0 0 4px var(--xeno-border);
60
+ }
61
+
62
+ /* Active drag reads a touch brighter than hover. */
63
+ .xeno-resizable[data-dragging='true'] .xeno-resizable-handle {
64
+ background: var(--xeno-active);
65
+ }
66
+
67
+ /* Disabled: inert hairline, not-allowed cursor, dimmed. */
68
+ .xeno-resizable[data-availability='disabled'] .xeno-resizable-handle,
69
+ .xeno-resizable-handle[data-availability='disabled'] {
70
+ background: var(--xeno-border);
71
+ cursor: not-allowed;
72
+ opacity: 0.4;
73
+ }
74
+
75
+ @media (prefers-reduced-motion: reduce) {
76
+ .xeno-resizable-handle {
77
+ transition: none;
78
+ }
79
+ }
@@ -0,0 +1,296 @@
1
+ /*
2
+ * Sidebar — the slide-in left navigation panel. Locked geometry: SQUARE corners, flush to the viewport
3
+ * edge; a fixed ~260px column on `var(--xeno-surface)`. CSS-first: the only state that drives appearance
4
+ * is `data-state="open|closed"` on the panel + backdrop. The only animated property is `transform`
5
+ * (translateX), guarded by reduced-motion. Monochrome — the scrim is a neutral, hueless black.
6
+ */
7
+
8
+ .xeno-sidebar {
9
+ /* One row's stride, in one place: the pill's travel is computed from these, so a row that changes
10
+ height can never leave the pill landing between two rows. */
11
+ --xeno-sidebar-row: 36px;
12
+ --xeno-sidebar-row-gap: 2px;
13
+ /* How much the goo pill misbehaves. The squash is deliberately restrained — enough deformation to
14
+ read as mass, not enough to look like jelly on a nav you use all day — and the travel easing
15
+ overshoots only slightly (1.12, against the 1.4 of the library's overshoot token). Turn these
16
+ three up together to get the full CodePen bounce back. */
17
+ --xeno-goo-squash-x: 0.95;
18
+ --xeno-goo-squash-y: 1.1;
19
+ --xeno-goo-ease: cubic-bezier(0.34, 1.12, 0.5, 1);
20
+ position: fixed;
21
+ inset-block: 0;
22
+ inset-inline-start: 0;
23
+ z-index: 40;
24
+ display: flex;
25
+ flex-direction: column;
26
+ width: 260px;
27
+ max-width: 85vw;
28
+ height: 100%;
29
+ background: var(--xeno-surface);
30
+ border-inline-end: 1px solid var(--xeno-border);
31
+ border-radius: 0; /* locked: square, flush */
32
+ color: var(--xeno-text);
33
+ transform: translateX(0);
34
+ transition: transform var(--xeno-dur) var(--xeno-ease);
35
+ will-change: transform;
36
+ }
37
+ .xeno-sidebar[data-state='closed'] {
38
+ transform: translateX(-100%);
39
+ }
40
+
41
+ /* ── Header (brand + search) ─────────────────────────────────────────────────────── */
42
+ .xeno-sidebar-header {
43
+ display: flex;
44
+ flex-direction: column;
45
+ gap: 12px;
46
+ flex: none;
47
+ padding: 16px;
48
+ border-bottom: 1px solid var(--xeno-border);
49
+ }
50
+ .xeno-sidebar-brand {
51
+ font-size: 15px;
52
+ font-weight: 600;
53
+ line-height: 1;
54
+ letter-spacing: 0.04em;
55
+ color: var(--xeno-text);
56
+ }
57
+ /* Make the shared TextInput fill the header width. */
58
+ .xeno-sidebar-search {
59
+ display: flex;
60
+ width: 100%;
61
+ }
62
+
63
+ /* ── Nav (scrollable list of rows) ───────────────────────────────────────────────── */
64
+ .xeno-sidebar-nav {
65
+ flex: 1;
66
+ min-height: 0;
67
+ overflow-y: auto;
68
+ padding: 8px;
69
+ }
70
+ .xeno-sidebar-section {
71
+ display: flex;
72
+ flex-direction: column;
73
+ gap: var(--xeno-sidebar-row-gap);
74
+ }
75
+ /* The rows of one group + the pill that backs them. `position: relative` makes this the pill's
76
+ containing block, so its travel is measured from the first row of THIS group — which is why the
77
+ heading can sit outside and the trick survives sections of different shapes. */
78
+ .xeno-sidebar-items {
79
+ position: relative;
80
+ display: flex;
81
+ flex-direction: column;
82
+ gap: var(--xeno-sidebar-row-gap);
83
+ }
84
+ .xeno-sidebar-section + .xeno-sidebar-section {
85
+ margin-top: 8px;
86
+ }
87
+ .xeno-sidebar-heading {
88
+ padding: 10px 10px 4px;
89
+ font-size: 11px;
90
+ font-weight: 600;
91
+ line-height: 1;
92
+ letter-spacing: 0.08em;
93
+ text-transform: uppercase;
94
+ color: var(--xeno-muted);
95
+ }
96
+
97
+ /* ── Row (self-contained; NOT ListRow) ───────────────────────────────────────────── */
98
+ .xeno-sidebar-item {
99
+ position: relative;
100
+ z-index: 1; /* over the pill, which is painted behind the whole group */
101
+ display: flex;
102
+ align-items: center;
103
+ gap: 10px;
104
+ width: 100%;
105
+ height: var(--xeno-sidebar-row);
106
+ padding: 0 10px;
107
+ font-family: inherit;
108
+ font-size: 14px;
109
+ font-weight: 500;
110
+ line-height: 1;
111
+ text-align: left;
112
+ color: var(--xeno-muted);
113
+ background: transparent;
114
+ border: 1px solid transparent;
115
+ border-radius: var(--xeno-radius-md);
116
+ cursor: pointer;
117
+ user-select: none;
118
+ transition:
119
+ background var(--xeno-dur) ease,
120
+ color var(--xeno-dur) ease;
121
+ }
122
+ /* Neither hover nor selection paints its OWN background any more — the travelling pill below is the
123
+ single filled surface for the whole group. Two of them would read as two selections. */
124
+ .xeno-sidebar-item:hover,
125
+ .xeno-sidebar-item[data-selection='on'] {
126
+ color: var(--xeno-text);
127
+ }
128
+ .xeno-sidebar-item:focus-visible {
129
+ outline: none;
130
+ box-shadow:
131
+ 0 0 0 2px var(--xeno-surface),
132
+ 0 0 0 4px var(--xeno-border);
133
+ }
134
+ .xeno-sidebar-item[data-availability='disabled'] {
135
+ opacity: 0.4;
136
+ cursor: not-allowed;
137
+ }
138
+
139
+ /* ── The goo pill (the travelling hover surface) ──────────────────────────────────────
140
+ * ONE filled pill per group, painted behind the rows, that FLIES to whichever row you point at and
141
+ * squashes on the way — narrower and taller as it launches, back to true as it lands. That squash is
142
+ * the whole illusion: a rectangle that only slid would read as a scrollbar; one that deforms reads as
143
+ * something with mass. Adapted from One_div's gooey nav, in XENO ink: the pill is `--xeno-control`,
144
+ * the monochrome step above the panel, not an accent colour.
145
+ *
146
+ * Pure CSS, no measuring: the pill is the LAST child of the group, so every row keeps its natural
147
+ * `:nth-child` number and a hovered row reaches the pill with `~`. Travel is `row index x stride`, and
148
+ * the stride comes from the row tokens above, so it cannot drift out of step with the layout. */
149
+ .xeno-sidebar-goo {
150
+ position: absolute;
151
+ z-index: 0;
152
+ left: 0;
153
+ top: calc(var(--xeno-goo-row, 0) * (var(--xeno-sidebar-row) + var(--xeno-sidebar-row-gap)));
154
+ width: 100%;
155
+ height: var(--xeno-sidebar-row);
156
+ background: var(--xeno-control);
157
+ border-radius: var(--xeno-radius-md);
158
+ opacity: 0;
159
+ pointer-events: none;
160
+ transition:
161
+ top var(--xeno-dur-entrance, 240ms) var(--xeno-goo-ease),
162
+ opacity var(--xeno-dur, 0.14s) ease;
163
+ }
164
+ /* The pill RESTS on the selected row of its group (so the selection indicator is the same object
165
+ that flies), and is invisible in a group with no selection. */
166
+ .xeno-sidebar-item[data-selection='on'] ~ .xeno-sidebar-goo { opacity: 1; }
167
+ .xeno-sidebar-item[data-selection='on']:nth-child(1) ~ .xeno-sidebar-goo { --xeno-goo-row: 0; }
168
+ .xeno-sidebar-item[data-selection='on']:nth-child(2) ~ .xeno-sidebar-goo { --xeno-goo-row: 1; }
169
+ .xeno-sidebar-item[data-selection='on']:nth-child(3) ~ .xeno-sidebar-goo { --xeno-goo-row: 2; }
170
+ .xeno-sidebar-item[data-selection='on']:nth-child(4) ~ .xeno-sidebar-goo { --xeno-goo-row: 3; }
171
+ .xeno-sidebar-item[data-selection='on']:nth-child(5) ~ .xeno-sidebar-goo { --xeno-goo-row: 4; }
172
+ .xeno-sidebar-item[data-selection='on']:nth-child(6) ~ .xeno-sidebar-goo { --xeno-goo-row: 5; }
173
+ .xeno-sidebar-item[data-selection='on']:nth-child(7) ~ .xeno-sidebar-goo { --xeno-goo-row: 6; }
174
+ .xeno-sidebar-item[data-selection='on']:nth-child(8) ~ .xeno-sidebar-goo { --xeno-goo-row: 7; }
175
+ .xeno-sidebar-item[data-selection='on']:nth-child(9) ~ .xeno-sidebar-goo { --xeno-goo-row: 8; }
176
+ .xeno-sidebar-item[data-selection='on']:nth-child(10) ~ .xeno-sidebar-goo { --xeno-goo-row: 9; }
177
+ .xeno-sidebar-item[data-selection='on']:nth-child(11) ~ .xeno-sidebar-goo { --xeno-goo-row: 10; }
178
+ .xeno-sidebar-item[data-selection='on']:nth-child(12) ~ .xeno-sidebar-goo { --xeno-goo-row: 11; }
179
+
180
+ /* Hover takes it over. Same specificity as the rules above ON PURPOSE — these come later, so pointing
181
+ at a row always wins over the resting position, and letting go hands it back. */
182
+ .xeno-sidebar-item:hover ~ .xeno-sidebar-goo { opacity: 1; }
183
+ .xeno-sidebar-item:nth-child(1):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 0; animation: xeno-goo-1 var(--xeno-dur-entrance, 240ms) 1; }
184
+ .xeno-sidebar-item:nth-child(2):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 1; animation: xeno-goo-2 var(--xeno-dur-entrance, 240ms) 1; }
185
+ .xeno-sidebar-item:nth-child(3):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 2; animation: xeno-goo-3 var(--xeno-dur-entrance, 240ms) 1; }
186
+ .xeno-sidebar-item:nth-child(4):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 3; animation: xeno-goo-4 var(--xeno-dur-entrance, 240ms) 1; }
187
+ .xeno-sidebar-item:nth-child(5):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 4; animation: xeno-goo-5 var(--xeno-dur-entrance, 240ms) 1; }
188
+ .xeno-sidebar-item:nth-child(6):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 5; animation: xeno-goo-6 var(--xeno-dur-entrance, 240ms) 1; }
189
+ .xeno-sidebar-item:nth-child(7):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 6; animation: xeno-goo-7 var(--xeno-dur-entrance, 240ms) 1; }
190
+ .xeno-sidebar-item:nth-child(8):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 7; animation: xeno-goo-8 var(--xeno-dur-entrance, 240ms) 1; }
191
+ .xeno-sidebar-item:nth-child(9):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 8; animation: xeno-goo-9 var(--xeno-dur-entrance, 240ms) 1; }
192
+ .xeno-sidebar-item:nth-child(10):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 9; animation: xeno-goo-10 var(--xeno-dur-entrance, 240ms) 1; }
193
+ .xeno-sidebar-item:nth-child(11):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 10; animation: xeno-goo-11 var(--xeno-dur-entrance, 240ms) 1; }
194
+ .xeno-sidebar-item:nth-child(12):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 11; animation: xeno-goo-12 var(--xeno-dur-entrance, 240ms) 1; }
195
+
196
+ /* One keyframe track PER ROW, all identical. They have to be separate NAMES: re-pointing at another row
197
+ only swaps which rule matches, and a browser does not restart an animation whose name did not change —
198
+ so a single shared `xeno-goo` would squash once and then never again as you slide down the list. */
199
+ @keyframes xeno-goo-1 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
200
+ @keyframes xeno-goo-2 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
201
+ @keyframes xeno-goo-3 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
202
+ @keyframes xeno-goo-4 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
203
+ @keyframes xeno-goo-5 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
204
+ @keyframes xeno-goo-6 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
205
+ @keyframes xeno-goo-7 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
206
+ @keyframes xeno-goo-8 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
207
+ @keyframes xeno-goo-9 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
208
+ @keyframes xeno-goo-10 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
209
+ @keyframes xeno-goo-11 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
210
+ @keyframes xeno-goo-12 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
211
+
212
+ /* ── The XENO nav hover animation ─────────────────────────────────────────────────────
213
+ * The icon + label NUDGE right — the icon leads, the label follows a beat later (a small cascade), on
214
+ * the interaction easing (E2). There is no left rail: the goo pill above already marks both the hovered
215
+ * row and the active one, and a second marker on the same edge only competed with it. */
216
+ .xeno-sidebar-item-icon {
217
+ display: inline-flex;
218
+ flex: none;
219
+ color: var(--xeno-muted);
220
+ transition:
221
+ color var(--xeno-dur) ease,
222
+ transform var(--xeno-dur-entrance, 240ms) var(--xeno-ease-stagger, cubic-bezier(0.22, 0.7, 0.2, 1));
223
+ }
224
+ .xeno-sidebar-item-label {
225
+ min-width: 0;
226
+ overflow: hidden;
227
+ text-overflow: ellipsis;
228
+ white-space: nowrap;
229
+ transition: transform var(--xeno-dur-entrance, 240ms) var(--xeno-ease-stagger, cubic-bezier(0.22, 0.7, 0.2, 1));
230
+ }
231
+ .xeno-sidebar-item:hover .xeno-sidebar-item-icon,
232
+ .xeno-sidebar-item[data-selection='on'] .xeno-sidebar-item-icon {
233
+ color: var(--xeno-text);
234
+ }
235
+ .xeno-sidebar-item:hover .xeno-sidebar-item-icon {
236
+ transform: translateX(6px);
237
+ }
238
+ .xeno-sidebar-item:hover .xeno-sidebar-item-label {
239
+ transform: translateX(6px);
240
+ transition-delay: 45ms; /* label follows the icon — the cascade */
241
+ }
242
+
243
+ /* Per-glyph icon animations are global now — see `icon-motion.css`, triggered by
244
+ * `.xeno-sidebar-item:hover .xeno-element[data-glyph]`. The nudge above stays sidebar-specific. */
245
+
246
+ /* ── Footer ──────────────────────────────────────────────────────────────────────── */
247
+ .xeno-sidebar-footer {
248
+ flex: none;
249
+ padding: 12px 16px;
250
+ border-top: 1px solid var(--xeno-border);
251
+ }
252
+
253
+ /* ── Backdrop (small screens only) ───────────────────────────────────────────────── */
254
+ .xeno-sidebar-backdrop {
255
+ position: fixed;
256
+ inset: 0;
257
+ z-index: 39;
258
+ background: rgba(0, 0, 0, 0.5); /* neutral scrim — hueless, like a shadow */
259
+ opacity: 0;
260
+ visibility: hidden;
261
+ transition:
262
+ opacity var(--xeno-dur) ease,
263
+ visibility var(--xeno-dur) ease;
264
+ }
265
+ .xeno-sidebar-backdrop[data-state='open'] {
266
+ opacity: 1;
267
+ visibility: visible;
268
+ }
269
+ @media (min-width: 900px) {
270
+ .xeno-sidebar-backdrop {
271
+ display: none;
272
+ }
273
+ }
274
+
275
+ @media (prefers-reduced-motion: reduce) {
276
+ .xeno-sidebar,
277
+ .xeno-sidebar-backdrop,
278
+ .xeno-sidebar-item,
279
+ .xeno-sidebar-item-icon,
280
+ .xeno-sidebar-item-label,
281
+ .xeno-sidebar-goo {
282
+ transition: none;
283
+ }
284
+ /* The pill still MARKS the row it belongs to — it just gets there without flying or squashing. */
285
+ .xeno-sidebar-goo {
286
+ animation: none !important;
287
+ }
288
+ /* States still apply (the pill marks the row, colours change); motion does not — no nudge, no per-glyph play. */
289
+ .xeno-sidebar-item:hover .xeno-sidebar-item-icon,
290
+ .xeno-sidebar-item:hover .xeno-sidebar-item-label {
291
+ transform: none;
292
+ }
293
+ .xeno-sidebar-item-icon .xeno-element {
294
+ animation: none !important;
295
+ }
296
+ }
@@ -0,0 +1,238 @@
1
+ /*
2
+ * DateTimePicker — a faithful port of the chat's "Scheduled" date/time popover. Monochrome, tokens
3
+ * only, `rounded-md` (6px) everywhere. The SELECTION is a soft `--xeno-control` fill (never bright
4
+ * inverted ink); adjacent-month days are greyed; the time columns are quiet bordered lists with a thin
5
+ * scrollbar, and AM/PM is two flat buttons. State is CSS-first via `data-selection` on the cells.
6
+ */
7
+
8
+ /* ── DatePicker ──────────────────────────────────────────────────────────────────── */
9
+ .xeno-datepicker {
10
+ --xeno-cell: 26px;
11
+ display: inline-flex;
12
+ flex-direction: column;
13
+ gap: 3px;
14
+ width: fit-content;
15
+ padding: 6px;
16
+ background: var(--xeno-surface);
17
+ border: 1px solid var(--xeno-border);
18
+ border-radius: var(--xeno-radius-md);
19
+ color: var(--xeno-text);
20
+ user-select: none;
21
+ }
22
+
23
+ .xeno-datepicker-header {
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: space-between;
27
+ gap: 8px;
28
+ padding: 2px 2px 0;
29
+ }
30
+
31
+ .xeno-datepicker-title {
32
+ flex: 1 1 auto;
33
+ min-width: 0;
34
+ text-align: center;
35
+ font-size: 12px;
36
+ font-weight: 600;
37
+ line-height: 1.2;
38
+ white-space: nowrap;
39
+ overflow: hidden;
40
+ text-overflow: ellipsis;
41
+ color: var(--xeno-text);
42
+ }
43
+
44
+ /* The prev/next chevrons are one glyph (chevron-right); the prev button mirrors it to point left. */
45
+ .xeno-datepicker-prev .xeno-element {
46
+ transform: rotate(180deg);
47
+ }
48
+
49
+ .xeno-datepicker-weekdays {
50
+ display: grid;
51
+ grid-template-columns: repeat(7, var(--xeno-cell));
52
+ gap: 2px;
53
+ }
54
+
55
+ .xeno-datepicker-weekday {
56
+ display: flex;
57
+ align-items: center;
58
+ justify-content: center;
59
+ height: 16px;
60
+ font-size: 10px;
61
+ font-weight: 500;
62
+ color: var(--xeno-muted);
63
+ }
64
+
65
+ .xeno-datepicker-grid {
66
+ display: flex;
67
+ flex-direction: column;
68
+ gap: 2px;
69
+ }
70
+
71
+ .xeno-datepicker-week {
72
+ display: grid;
73
+ grid-template-columns: repeat(7, var(--xeno-cell));
74
+ gap: 2px;
75
+ }
76
+
77
+ .xeno-datepicker-cell {
78
+ width: var(--xeno-cell);
79
+ height: var(--xeno-cell);
80
+ }
81
+
82
+ .xeno-datepicker-day {
83
+ display: inline-flex;
84
+ align-items: center;
85
+ justify-content: center;
86
+ padding: 0;
87
+ font-family: inherit;
88
+ font-size: 12px;
89
+ font-weight: 500;
90
+ font-variant-numeric: tabular-nums;
91
+ color: var(--xeno-text);
92
+ background: transparent;
93
+ border: 1px solid transparent;
94
+ border-radius: var(--xeno-radius-md);
95
+ cursor: pointer;
96
+ transition:
97
+ background var(--xeno-dur) ease,
98
+ color var(--xeno-dur) ease;
99
+ }
100
+ .xeno-datepicker-day:hover {
101
+ background: var(--xeno-hover);
102
+ }
103
+ .xeno-datepicker-day:focus-visible {
104
+ outline: none;
105
+ box-shadow:
106
+ 0 0 0 2px var(--xeno-surface),
107
+ 0 0 0 4px var(--xeno-border);
108
+ }
109
+ /* Adjacent-month days: present (real, clickable) but greyed back. */
110
+ .xeno-datepicker-day[data-outside] {
111
+ color: var(--xeno-muted);
112
+ opacity: 0.45;
113
+ }
114
+ /* Selection — the soft control fill the product uses, not bright inverted ink. */
115
+ .xeno-datepicker-day[data-selection='on'] {
116
+ color: var(--xeno-text);
117
+ opacity: 1;
118
+ background: var(--xeno-control);
119
+ }
120
+
121
+ .xeno-datepicker-footer {
122
+ display: flex;
123
+ align-items: center;
124
+ justify-content: space-between;
125
+ gap: 8px;
126
+ margin-top: 2px;
127
+ padding-top: 6px;
128
+ border-top: 1px solid var(--xeno-border);
129
+ }
130
+ /* If Clear is hidden, Today still sits at the right edge. */
131
+ .xeno-datepicker-footer > .xeno-datepicker-today:only-child {
132
+ margin-left: auto;
133
+ }
134
+
135
+ /* ── TimePicker ──────────────────────────────────────────────────────────────────── */
136
+ .xeno-timepicker {
137
+ display: flex;
138
+ gap: 4px;
139
+ width: fit-content;
140
+ /* Match the calendar's natural height (26px cells → ~258px) so the two fields' panels are consistent
141
+ in size; the Hour/Min lists fill and scroll within it. */
142
+ min-height: 258px;
143
+ padding: 6px;
144
+ background: var(--xeno-surface);
145
+ border: 1px solid var(--xeno-border);
146
+ border-radius: var(--xeno-radius-md);
147
+ color: var(--xeno-text);
148
+ user-select: none;
149
+ }
150
+
151
+ /* Equal-width columns that stretch to the panel height, so all three boxes match — and the panel can
152
+ grow to match a taller sibling (e.g. the calendar) with the lists filling it, not leaving a gap. */
153
+ .xeno-timepicker-col {
154
+ display: flex;
155
+ flex: 1 1 0;
156
+ flex-direction: column;
157
+ gap: 4px;
158
+ min-width: 58px;
159
+ min-height: 0;
160
+ }
161
+
162
+ .xeno-timepicker-col-head {
163
+ flex: 0 0 auto;
164
+ padding: 0 2px;
165
+ font-size: 10px;
166
+ font-weight: 500;
167
+ letter-spacing: 0.06em;
168
+ text-transform: uppercase;
169
+ color: var(--xeno-muted);
170
+ }
171
+
172
+ .xeno-timepicker-list {
173
+ display: flex;
174
+ /* basis 0: the list FILLS the column height without its content inflating the panel — so the
175
+ calendar drives the shared height and the time panel matches it exactly (scrolling within). */
176
+ flex: 1 1 0;
177
+ flex-direction: column;
178
+ gap: 2px;
179
+ min-height: 0;
180
+ padding: 2px;
181
+ border: 1px solid var(--xeno-border);
182
+ border-radius: var(--xeno-radius-md);
183
+ }
184
+ /* Hour + Min scroll within the shared height; AM/PM (no data-scroll) sits flush at the top.
185
+ The scrollbar itself comes from `scrollbar.css`; a column this narrow only wants a slimmer one, so
186
+ it re-declares the SIZE TOKEN and nothing else. It used to re-implement the whole thing — and set
187
+ `scrollbar-width` alongside its own `::-webkit-scrollbar` rules, which since Chrome 121 means Blink
188
+ ignored every one of them and drew the default bar. */
189
+ .xeno-timepicker-list[data-scroll] {
190
+ --xeno-scroll-size: 9px;
191
+ --xeno-scroll-pad: 2px;
192
+ --xeno-scroll-pad-hover: 1px;
193
+ --xeno-scroll-rail-pad: 4px; /* 9 − 2×4 = a 1px rail; the default 5 would leave none at all */
194
+ overflow-y: auto;
195
+ }
196
+
197
+ .xeno-timepicker-option {
198
+ display: flex;
199
+ flex: 0 0 auto;
200
+ align-items: center;
201
+ justify-content: center;
202
+ height: 26px;
203
+ width: 100%;
204
+ padding: 0;
205
+ font-family: inherit;
206
+ font-size: 12px;
207
+ font-weight: 500;
208
+ font-variant-numeric: tabular-nums;
209
+ color: var(--xeno-muted);
210
+ background: transparent;
211
+ border: none;
212
+ border-radius: var(--xeno-radius-sm);
213
+ cursor: pointer;
214
+ transition:
215
+ background var(--xeno-dur) ease,
216
+ color var(--xeno-dur) ease;
217
+ }
218
+ .xeno-timepicker-option:hover {
219
+ color: var(--xeno-text);
220
+ background: var(--xeno-hover);
221
+ }
222
+ .xeno-timepicker-option:focus-visible {
223
+ outline: none;
224
+ box-shadow:
225
+ 0 0 0 2px var(--xeno-surface),
226
+ 0 0 0 4px var(--xeno-border);
227
+ }
228
+ .xeno-timepicker-option[data-selection='on'] {
229
+ color: var(--xeno-text);
230
+ background: var(--xeno-control);
231
+ }
232
+
233
+ @media (prefers-reduced-motion: reduce) {
234
+ .xeno-datepicker-day,
235
+ .xeno-timepicker-option {
236
+ transition: none;
237
+ }
238
+ }