@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,72 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ══════════════════════════════════════════════════════════════════════════════════════════════
|
|
3
|
+
* OWNED FILE — SOFT (`unified`)
|
|
4
|
+
* ══════════════════════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
*
|
|
6
|
+
* One construction, one file, one owner, so the two can be worked on in parallel without either
|
|
7
|
+
* side touching the other's values. Everything a construction decides lives HERE — colours, box
|
|
8
|
+
* metrics, typography, radii. Nothing construction-specific belongs in a component stylesheet or
|
|
9
|
+
* in `xeno-theme.css`, because those are shared and a change there hits BOTH looks.
|
|
10
|
+
*
|
|
11
|
+
* THE CONTRACT BETWEEN THE TWO FILES, enforced by `theme.test.ts`:
|
|
12
|
+
* 1. Every token declared in `chrome-separated.css` must also be declared here, and vice versa.
|
|
13
|
+
* Adding one on your side and not telling the other side is the failure mode — the missing
|
|
14
|
+
* one silently inherits the other construction's value and half-renders.
|
|
15
|
+
* 2. Every token must actually DIFFER between the two files. If a value is the same in both, it
|
|
16
|
+
* is not a construction decision: move it to the shared base.
|
|
17
|
+
*
|
|
18
|
+
* So adding a token is a two-file change and the suite will say so. That is deliberate: it is the
|
|
19
|
+
* cheapest possible coordination between two people editing in parallel.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/* ── UNIFIED chrome ────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
* One clipped surface with hairline dividers — the construction this set shipped before the chrome
|
|
24
|
+
* axis existed, preserved deliberately rather than corrected away.
|
|
25
|
+
*
|
|
26
|
+
* It is expressed ENTIRELY as values. Collapsing the gap to 0, painting the shell with the surface
|
|
27
|
+
* colour and making the plates transparent reproduces a single card exactly — same DOM, same
|
|
28
|
+
* components, no second implementation. That is the whole test for whether something may be a theme.
|
|
29
|
+
*/
|
|
30
|
+
[data-chrome='unified'] {
|
|
31
|
+
--xeno-chrome-gap: 0px;
|
|
32
|
+
--xeno-chrome-inset: 0px;
|
|
33
|
+
--xeno-chrome-shell: var(--xeno-surface);
|
|
34
|
+
--xeno-chrome-plate: transparent;
|
|
35
|
+
--xeno-chrome-body: transparent;
|
|
36
|
+
--xeno-chrome-divider: 1px solid var(--xeno-border);
|
|
37
|
+
--xeno-chrome-shadow: 0 12px 48px -16px rgba(0, 0, 0, 0.55);
|
|
38
|
+
--xeno-chrome-radius: var(--xeno-radius-card);
|
|
39
|
+
--xeno-chrome-radius-plate: 0px;
|
|
40
|
+
/* No glass: a single clipped surface has nothing behind a plate to blur. */
|
|
41
|
+
--xeno-chrome-plate-filter: none;
|
|
42
|
+
/* The inverted primary this set already shipped — preserved, not corrected. */
|
|
43
|
+
/* The soft card this set shipped — surface, big radius, hairline, and a real shadow. */
|
|
44
|
+
--xeno-chrome-card-bg: var(--xeno-surface);
|
|
45
|
+
--xeno-chrome-card-border: 1px solid var(--xeno-border);
|
|
46
|
+
--xeno-chrome-card-radius: var(--xeno-radius-card);
|
|
47
|
+
--xeno-chrome-card-pad: 13px 15px;
|
|
48
|
+
--xeno-chrome-card-shadow: 0 1px 2px rgba(0, 0, 0, 0.28);
|
|
49
|
+
--xeno-chrome-card-lift: 0 8px 24px -10px rgba(0, 0, 0, 0.45);
|
|
50
|
+
|
|
51
|
+
/* Restores every pre-axis value exactly. Verified against the baseline, not eyeballed. */
|
|
52
|
+
--xeno-chrome-border: var(--xeno-border);
|
|
53
|
+
--xeno-chrome-panel-header-pad: 11px 15px;
|
|
54
|
+
--xeno-chrome-panel-header-min-h: auto;
|
|
55
|
+
--xeno-chrome-panel-body-pad: 15px;
|
|
56
|
+
--xeno-chrome-title-size: 14px;
|
|
57
|
+
--xeno-chrome-title-color: var(--xeno-text);
|
|
58
|
+
--xeno-chrome-title-transform: none;
|
|
59
|
+
--xeno-chrome-title-spacing: normal;
|
|
60
|
+
--xeno-chrome-dialog-header-pad: 16px 16px 12px 20px;
|
|
61
|
+
--xeno-chrome-dialog-title-size: 16px;
|
|
62
|
+
--xeno-chrome-dialog-title-weight: 600;
|
|
63
|
+
--xeno-chrome-dialog-body-pad: 4px 20px 20px;
|
|
64
|
+
--xeno-chrome-dialog-body-size: 14px;
|
|
65
|
+
--xeno-chrome-dialog-footer-pad: 12px 20px;
|
|
66
|
+
--xeno-chrome-dialog-footer-gap: 8px;
|
|
67
|
+
--xeno-chrome-card-elevated-bg: var(--xeno-elevated);
|
|
68
|
+
|
|
69
|
+
--xeno-chrome-btn-primary-bg: var(--xeno-text);
|
|
70
|
+
--xeno-chrome-btn-primary-fg: var(--xeno-on-accent);
|
|
71
|
+
--xeno-chrome-btn-radius: var(--xeno-radius-control);
|
|
72
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Badges — inline monochrome markers (Badge / StatusPill / CountBadge / CitationBadge).
|
|
3
|
+
*
|
|
4
|
+
* CSS-first, driven by the theme custom properties from `xeno-theme.css`. These elements are STATIC
|
|
5
|
+
* (no interaction), so the only state seam is `data-tone` on the tonal members. Colours stay in the
|
|
6
|
+
* monochrome shell; the sole exception is the StatusPill status hue — `success`/`warning` arrive as
|
|
7
|
+
* the inline `--xeno-pill-tone` var (set from the status token by the renderer) and `danger` maps to
|
|
8
|
+
* `--xeno-danger`. Grammar: rounded squares, never circles. No literal colours here.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/* ── Badge ─────────────────────────────────────────────────────────────────────────── */
|
|
12
|
+
.xeno-badge {
|
|
13
|
+
display: inline-flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
gap: 4px;
|
|
16
|
+
height: 16px;
|
|
17
|
+
padding: 0 6px;
|
|
18
|
+
font-family: inherit;
|
|
19
|
+
font-size: 10px;
|
|
20
|
+
font-weight: 600;
|
|
21
|
+
line-height: 1;
|
|
22
|
+
letter-spacing: 0.05em;
|
|
23
|
+
text-transform: uppercase;
|
|
24
|
+
white-space: nowrap;
|
|
25
|
+
color: var(--xeno-muted);
|
|
26
|
+
background: var(--xeno-surface);
|
|
27
|
+
border: 1px solid var(--xeno-border);
|
|
28
|
+
border-radius: var(--xeno-radius-sm);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* ── StatusPill ────────────────────────────────────────────────────────────────────── */
|
|
32
|
+
.xeno-statuspill {
|
|
33
|
+
display: inline-flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
gap: 5px;
|
|
36
|
+
height: 16px;
|
|
37
|
+
padding: 0 6px;
|
|
38
|
+
font-family: inherit;
|
|
39
|
+
font-size: 10px;
|
|
40
|
+
font-weight: 600;
|
|
41
|
+
line-height: 1;
|
|
42
|
+
letter-spacing: 0.05em;
|
|
43
|
+
text-transform: uppercase;
|
|
44
|
+
white-space: nowrap;
|
|
45
|
+
color: var(--xeno-muted);
|
|
46
|
+
background: var(--xeno-surface);
|
|
47
|
+
border: 1px solid var(--xeno-border);
|
|
48
|
+
border-radius: var(--xeno-radius-sm);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* leading dot — a rounded SQUARE (never a circle), inheriting the pill tone via currentColor */
|
|
52
|
+
.xeno-statuspill-dot {
|
|
53
|
+
flex: none;
|
|
54
|
+
width: 5px;
|
|
55
|
+
height: 5px;
|
|
56
|
+
background: currentColor;
|
|
57
|
+
border-radius: var(--xeno-radius-hair);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* success / warning: colour text + border from the injected status-token var */
|
|
61
|
+
.xeno-statuspill[data-tone='success'],
|
|
62
|
+
.xeno-statuspill[data-tone='warning'] {
|
|
63
|
+
color: var(--xeno-pill-tone);
|
|
64
|
+
border-color: color-mix(in srgb, var(--xeno-pill-tone) 45%, transparent);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* danger: the one status hue that IS a theme token */
|
|
68
|
+
.xeno-statuspill[data-tone='danger'] {
|
|
69
|
+
color: var(--xeno-danger);
|
|
70
|
+
border-color: color-mix(in srgb, var(--xeno-danger) 45%, transparent);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* ── CountBadge ────────────────────────────────────────────────────────────────────── */
|
|
74
|
+
.xeno-countbadge {
|
|
75
|
+
display: inline-flex;
|
|
76
|
+
align-items: center;
|
|
77
|
+
justify-content: center;
|
|
78
|
+
box-sizing: border-box;
|
|
79
|
+
min-width: 16px;
|
|
80
|
+
height: 16px;
|
|
81
|
+
padding: 0 4px;
|
|
82
|
+
font-family: inherit;
|
|
83
|
+
font-size: 10px;
|
|
84
|
+
font-weight: 600;
|
|
85
|
+
line-height: 1;
|
|
86
|
+
font-variant-numeric: tabular-nums;
|
|
87
|
+
white-space: nowrap;
|
|
88
|
+
color: var(--xeno-text);
|
|
89
|
+
background: var(--xeno-control);
|
|
90
|
+
border: 1px solid var(--xeno-border);
|
|
91
|
+
border-radius: var(--xeno-radius-sm);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* ── CitationBadge ─────────────────────────────────────────────────────────────────── */
|
|
95
|
+
.xeno-citation {
|
|
96
|
+
display: inline-flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
box-sizing: border-box;
|
|
99
|
+
height: 16px;
|
|
100
|
+
padding: 0 4px;
|
|
101
|
+
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
|
|
102
|
+
font-size: 11px;
|
|
103
|
+
font-weight: 500;
|
|
104
|
+
line-height: 1;
|
|
105
|
+
white-space: nowrap;
|
|
106
|
+
vertical-align: baseline;
|
|
107
|
+
color: var(--xeno-muted);
|
|
108
|
+
background: var(--xeno-surface);
|
|
109
|
+
border: 1px solid var(--xeno-border);
|
|
110
|
+
border-radius: var(--xeno-radius-sm);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* the index reads brighter than its muted brackets */
|
|
114
|
+
.xeno-citation-index {
|
|
115
|
+
color: var(--xeno-text);
|
|
116
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Avatar + AvatarStack — a person/agent tile and its overlapping cluster. CSS-first: the square edge is
|
|
3
|
+
* an inline `--xeno-avatar-size` var (set by the component from `size`), so metrics never drift from the
|
|
4
|
+
* data. Rounded SQUARES only (`radius-xs`), never circles. Monochrome shell; depth is a hairline border
|
|
5
|
+
* plus, in the stack, a flat canvas ring. No literal colours — theme tokens only.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
.xeno-avatar {
|
|
9
|
+
display: inline-flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
justify-content: center;
|
|
12
|
+
flex: none;
|
|
13
|
+
box-sizing: border-box;
|
|
14
|
+
width: var(--xeno-avatar-size, 24px);
|
|
15
|
+
height: var(--xeno-avatar-size, 24px);
|
|
16
|
+
border: 1px solid var(--xeno-border);
|
|
17
|
+
border-radius: var(--xeno-radius-xs);
|
|
18
|
+
background: var(--xeno-control);
|
|
19
|
+
color: var(--xeno-muted);
|
|
20
|
+
overflow: hidden;
|
|
21
|
+
user-select: none;
|
|
22
|
+
vertical-align: middle;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* Image fill — cover the square, clipped to its rounded corners. */
|
|
26
|
+
.xeno-avatar-img {
|
|
27
|
+
display: block;
|
|
28
|
+
width: 100%;
|
|
29
|
+
height: 100%;
|
|
30
|
+
object-fit: cover;
|
|
31
|
+
border-radius: inherit;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Initials + '+N' overflow text — centred, uppercase, muted, scaled off the edge length. */
|
|
35
|
+
.xeno-avatar-initials {
|
|
36
|
+
font-family: inherit;
|
|
37
|
+
font-size: calc(var(--xeno-avatar-size, 24px) * 0.4);
|
|
38
|
+
font-weight: 600;
|
|
39
|
+
line-height: 1;
|
|
40
|
+
letter-spacing: 0.02em;
|
|
41
|
+
color: var(--xeno-muted);
|
|
42
|
+
text-transform: uppercase;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Glyph fallback inherits the tile's muted ink via currentColor. */
|
|
46
|
+
.xeno-avatar[data-variant='icon'] {
|
|
47
|
+
color: var(--xeno-muted);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* ── AvatarStack ─────────────────────────────────────────────────────────────────── */
|
|
51
|
+
.xeno-avatar-stack {
|
|
52
|
+
display: inline-flex;
|
|
53
|
+
align-items: center;
|
|
54
|
+
vertical-align: middle;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Overlap: pull each tile left and ring it in the canvas so the stack reads as separated squares. */
|
|
58
|
+
.xeno-avatar-stack > .xeno-avatar {
|
|
59
|
+
position: relative;
|
|
60
|
+
margin-left: -8px;
|
|
61
|
+
box-shadow: 0 0 0 2px var(--xeno-canvas);
|
|
62
|
+
}
|
|
63
|
+
.xeno-avatar-stack > .xeno-avatar:first-child {
|
|
64
|
+
margin-left: 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* The '+N' tile sits on the elevated surface to distinguish it from the people tiles. */
|
|
68
|
+
.xeno-avatar-more {
|
|
69
|
+
background: var(--xeno-elevated);
|
|
70
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Card — the base surface box. Monochrome shell: background + hairline border + card radius; depth on the
|
|
3
|
+
* `elevated` variant is the darker `--xeno-elevated` token plus one soft (hue-free) shadow. The hover-lift
|
|
4
|
+
* is opt-in via `data-interactive` and its transform lives behind `prefers-reduced-motion` — the border
|
|
5
|
+
* brighten still applies when motion is reduced. Only theme tokens; no literal colours (shadow excepted).
|
|
6
|
+
*/
|
|
7
|
+
.xeno-card {
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
padding: var(--xeno-chrome-card-pad);
|
|
11
|
+
background: var(--xeno-chrome-card-bg);
|
|
12
|
+
border: var(--xeno-chrome-card-border);
|
|
13
|
+
border-radius: var(--xeno-chrome-card-radius);
|
|
14
|
+
color: var(--xeno-text);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.xeno-card[data-variant='elevated'] {
|
|
18
|
+
background: var(--xeno-chrome-card-elevated-bg);
|
|
19
|
+
box-shadow: var(--xeno-chrome-card-shadow);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* Body — the default slot. min-width:0 keeps flex-row children from overflowing the card. */
|
|
23
|
+
.xeno-card-body {
|
|
24
|
+
min-width: 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* Header — a titled slot split from the body by an inset hairline. */
|
|
28
|
+
.xeno-card-header {
|
|
29
|
+
margin-bottom: 11px;
|
|
30
|
+
padding-bottom: 11px;
|
|
31
|
+
border-bottom: var(--xeno-chrome-divider);
|
|
32
|
+
color: var(--xeno-text);
|
|
33
|
+
font-size: 14px;
|
|
34
|
+
font-weight: 600;
|
|
35
|
+
line-height: 1.2;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Footer — a trailing slot, mirror of the header, quieter text. */
|
|
39
|
+
.xeno-card-footer {
|
|
40
|
+
margin-top: 11px;
|
|
41
|
+
padding-top: 11px;
|
|
42
|
+
border-top: var(--xeno-chrome-divider);
|
|
43
|
+
color: var(--xeno-muted);
|
|
44
|
+
font-size: 13px;
|
|
45
|
+
line-height: 1.3;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Interactive — the sole hover-lift XENO allows. Border brighten is safe without motion. */
|
|
49
|
+
.xeno-card[data-interactive='true'] {
|
|
50
|
+
cursor: pointer;
|
|
51
|
+
}
|
|
52
|
+
.xeno-card[data-interactive='true']:hover {
|
|
53
|
+
border-color: var(--xeno-muted);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Strong lift — the artifact-card affordance: a bigger rise + one soft, hue-free shadow. The shadow is
|
|
57
|
+
safe without motion, so it sits outside the reduced-motion guard; only the transform is gated. */
|
|
58
|
+
.xeno-card[data-interactive='true'][data-lift='strong']:hover {
|
|
59
|
+
box-shadow: var(--xeno-chrome-card-lift);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
63
|
+
.xeno-card[data-interactive='true'] {
|
|
64
|
+
transition:
|
|
65
|
+
transform var(--xeno-dur) var(--xeno-ease),
|
|
66
|
+
border-color var(--xeno-dur) ease,
|
|
67
|
+
box-shadow var(--xeno-dur) ease;
|
|
68
|
+
}
|
|
69
|
+
.xeno-card[data-interactive='true']:hover {
|
|
70
|
+
transform: translateY(-1px);
|
|
71
|
+
}
|
|
72
|
+
.xeno-card[data-interactive='true'][data-lift='strong']:hover {
|
|
73
|
+
transform: translateY(-3px);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Chip — a small selectable/removable token. SQUARE (radius-control), 1px hairline border, monochrome
|
|
3
|
+
* shell. State is driven by `data-*` on the root; size metrics arrive as inline `--xeno-h/-padx/-gap/
|
|
4
|
+
* -font` vars set from the size tokens, so a size can never drift from the data. No literal colours.
|
|
5
|
+
*/
|
|
6
|
+
.xeno-chip {
|
|
7
|
+
display: inline-flex;
|
|
8
|
+
align-items: center;
|
|
9
|
+
height: var(--xeno-h, 32px);
|
|
10
|
+
max-width: 100%;
|
|
11
|
+
color: var(--xeno-text);
|
|
12
|
+
background: transparent;
|
|
13
|
+
border: 1px solid var(--xeno-border);
|
|
14
|
+
border-radius: var(--xeno-radius-control);
|
|
15
|
+
font-family: inherit;
|
|
16
|
+
font-size: var(--xeno-font, 14px);
|
|
17
|
+
font-weight: 500;
|
|
18
|
+
line-height: 1;
|
|
19
|
+
white-space: nowrap;
|
|
20
|
+
user-select: none;
|
|
21
|
+
overflow: hidden; /* clips inner hover fills to the rounded corners */
|
|
22
|
+
}
|
|
23
|
+
.xeno-chip[data-selection='on'] {
|
|
24
|
+
background: var(--xeno-control);
|
|
25
|
+
}
|
|
26
|
+
.xeno-chip[data-availability='disabled'] {
|
|
27
|
+
opacity: 0.4;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Body — the label region: a plain span, or a toggle <button> when selectable. */
|
|
31
|
+
.xeno-chip-body {
|
|
32
|
+
display: inline-flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
gap: var(--xeno-gap, 6px);
|
|
35
|
+
height: 100%;
|
|
36
|
+
min-width: 0;
|
|
37
|
+
padding: 0 var(--xeno-padx, 12px);
|
|
38
|
+
color: inherit;
|
|
39
|
+
background: transparent;
|
|
40
|
+
border: none;
|
|
41
|
+
font: inherit;
|
|
42
|
+
line-height: 1;
|
|
43
|
+
}
|
|
44
|
+
.xeno-chip[data-removable='true'] .xeno-chip-body {
|
|
45
|
+
padding-right: var(--xeno-gap, 6px);
|
|
46
|
+
}
|
|
47
|
+
.xeno-chip-label {
|
|
48
|
+
overflow: hidden;
|
|
49
|
+
text-overflow: ellipsis;
|
|
50
|
+
white-space: nowrap;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Selectable body toggles the selection axis (aria-pressed); hover/press are CSS, not attributes. */
|
|
54
|
+
.xeno-chip-select {
|
|
55
|
+
cursor: pointer;
|
|
56
|
+
transition:
|
|
57
|
+
background var(--xeno-dur) ease,
|
|
58
|
+
color var(--xeno-dur) ease;
|
|
59
|
+
}
|
|
60
|
+
.xeno-chip-select:hover:not(:disabled) {
|
|
61
|
+
background: var(--xeno-hover);
|
|
62
|
+
}
|
|
63
|
+
.xeno-chip-select:focus-visible {
|
|
64
|
+
outline: none;
|
|
65
|
+
box-shadow: inset 0 0 0 2px var(--xeno-border);
|
|
66
|
+
}
|
|
67
|
+
.xeno-chip-select:disabled {
|
|
68
|
+
cursor: not-allowed;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* Remove — a bare 'x' button; muted ink that resolves to text on hover. */
|
|
72
|
+
.xeno-chip-remove {
|
|
73
|
+
display: inline-flex;
|
|
74
|
+
align-items: center;
|
|
75
|
+
justify-content: center;
|
|
76
|
+
height: 100%;
|
|
77
|
+
padding: 0 6px;
|
|
78
|
+
color: var(--xeno-muted);
|
|
79
|
+
background: transparent;
|
|
80
|
+
border: none;
|
|
81
|
+
cursor: pointer;
|
|
82
|
+
transition:
|
|
83
|
+
background var(--xeno-dur) ease,
|
|
84
|
+
color var(--xeno-dur) ease;
|
|
85
|
+
}
|
|
86
|
+
.xeno-chip-remove:hover:not(:disabled) {
|
|
87
|
+
color: var(--xeno-text);
|
|
88
|
+
background: var(--xeno-hover);
|
|
89
|
+
}
|
|
90
|
+
.xeno-chip-remove:focus-visible {
|
|
91
|
+
outline: none;
|
|
92
|
+
box-shadow: inset 0 0 0 2px var(--xeno-border);
|
|
93
|
+
}
|
|
94
|
+
.xeno-chip-remove:disabled {
|
|
95
|
+
cursor: not-allowed;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@media (prefers-reduced-motion: reduce) {
|
|
99
|
+
.xeno-chip-select,
|
|
100
|
+
.xeno-chip-remove {
|
|
101
|
+
transition: none;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ListRow — a selectable / actionable list row. CSS-first: the `selection` and `availability` axes
|
|
3
|
+
* arrive as `data-*` attributes on the root; interaction (hover / press / focus) is pure CSS. Depth is
|
|
4
|
+
* hairline borders only — monochrome shell, no coloured glow. Only the theme tokens from
|
|
5
|
+
* `xeno-theme.css` are used; no literal colours.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
.xeno-list-row {
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
gap: 12px;
|
|
12
|
+
width: 100%;
|
|
13
|
+
box-sizing: border-box;
|
|
14
|
+
min-width: 0;
|
|
15
|
+
margin: 0;
|
|
16
|
+
padding: 8px 12px;
|
|
17
|
+
color: var(--xeno-text);
|
|
18
|
+
text-align: left;
|
|
19
|
+
background: transparent;
|
|
20
|
+
border: 1px solid transparent;
|
|
21
|
+
border-radius: var(--xeno-radius-md);
|
|
22
|
+
cursor: default;
|
|
23
|
+
transition:
|
|
24
|
+
background var(--xeno-dur) ease,
|
|
25
|
+
border-color var(--xeno-dur) ease;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Reset the native <button> chrome so a button row reads exactly like a div row. */
|
|
29
|
+
button.xeno-list-row {
|
|
30
|
+
font: inherit;
|
|
31
|
+
appearance: none;
|
|
32
|
+
-webkit-appearance: none;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Interactive rows (a real button, or a div carrying role=button / role=option) invite the pointer. */
|
|
36
|
+
button.xeno-list-row:not(:disabled),
|
|
37
|
+
.xeno-list-row[role='button'],
|
|
38
|
+
.xeno-list-row[role='option'] {
|
|
39
|
+
cursor: pointer;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Hover — only interactive, enabled, unselected rows lift onto --xeno-hover. */
|
|
43
|
+
button.xeno-list-row:not(:disabled):hover,
|
|
44
|
+
.xeno-list-row[role='button']:not([aria-disabled='true']):hover,
|
|
45
|
+
.xeno-list-row[role='option']:not([aria-disabled='true']):hover {
|
|
46
|
+
background: var(--xeno-hover);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Selection wins over hover — a selected row stays on the solid control fill. */
|
|
50
|
+
.xeno-list-row[data-selection='on'],
|
|
51
|
+
button.xeno-list-row[data-selection='on']:not(:disabled):hover,
|
|
52
|
+
.xeno-list-row[role='button'][data-selection='on']:hover,
|
|
53
|
+
.xeno-list-row[role='option'][data-selection='on']:hover {
|
|
54
|
+
background: var(--xeno-control);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.xeno-list-row:focus-visible {
|
|
58
|
+
outline: none;
|
|
59
|
+
box-shadow:
|
|
60
|
+
0 0 0 2px var(--xeno-canvas),
|
|
61
|
+
0 0 0 4px var(--xeno-border);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
button.xeno-list-row:active:not(:disabled) {
|
|
65
|
+
transform: scale(0.995);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.xeno-list-row:disabled,
|
|
69
|
+
.xeno-list-row[data-availability='disabled'] {
|
|
70
|
+
opacity: 0.4;
|
|
71
|
+
cursor: not-allowed;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* ── Slots ───────────────────────────────────────────────────────────────────────── */
|
|
75
|
+
|
|
76
|
+
/* Leading — glyph / avatar. Fixed, never shrinks. Its own children keep their intrinsic colour. */
|
|
77
|
+
.xeno-list-row-leading {
|
|
78
|
+
display: inline-flex;
|
|
79
|
+
align-items: center;
|
|
80
|
+
justify-content: center;
|
|
81
|
+
flex: none;
|
|
82
|
+
color: var(--xeno-muted);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* Middle — the truncation column. min-width:0 lets the title/subtitle ellipsize inside the flex row. */
|
|
86
|
+
.xeno-list-row-body {
|
|
87
|
+
display: flex;
|
|
88
|
+
flex: 1 1 auto;
|
|
89
|
+
flex-direction: column;
|
|
90
|
+
gap: 2px;
|
|
91
|
+
min-width: 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.xeno-list-row-title {
|
|
95
|
+
overflow: hidden;
|
|
96
|
+
font-size: 14px;
|
|
97
|
+
font-weight: 500;
|
|
98
|
+
line-height: 1.3;
|
|
99
|
+
color: var(--xeno-text);
|
|
100
|
+
white-space: nowrap;
|
|
101
|
+
text-overflow: ellipsis;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.xeno-list-row-subtitle {
|
|
105
|
+
overflow: hidden;
|
|
106
|
+
font-size: 12px;
|
|
107
|
+
font-weight: 400;
|
|
108
|
+
line-height: 1.3;
|
|
109
|
+
color: var(--xeno-muted);
|
|
110
|
+
white-space: nowrap;
|
|
111
|
+
text-overflow: ellipsis;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* Trailing — meta / actions pinned to the end. Fixed, never shrinks. */
|
|
115
|
+
.xeno-list-row-trailing {
|
|
116
|
+
display: inline-flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
gap: 6px;
|
|
119
|
+
flex: none;
|
|
120
|
+
margin-left: auto;
|
|
121
|
+
font-size: 12px;
|
|
122
|
+
color: var(--xeno-muted);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@media (prefers-reduced-motion: reduce) {
|
|
126
|
+
.xeno-list-row {
|
|
127
|
+
transition: none;
|
|
128
|
+
}
|
|
129
|
+
button.xeno-list-row:active:not(:disabled) {
|
|
130
|
+
transform: none;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* MessageBubble — the chat message container. Monochrome SHELL: the container carries no colour of its
|
|
3
|
+
* own; only `children` may bring content colour. A `user` turn is a right-aligned bubble; an `assistant`
|
|
4
|
+
* turn is a bare left-aligned prose block (the locked design). State rides the root as `data-role` and,
|
|
5
|
+
* while a turn is still being written, `data-streaming`. Only the theme custom properties are used here.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
.xeno-message {
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
gap: 6px;
|
|
12
|
+
max-width: 100%;
|
|
13
|
+
/* How wide a user bubble may run before it wraps. A knob rather than a prop, because the answer is
|
|
14
|
+
usually a MEDIA QUERY — a bubble that reads well at 80% on a desktop column is cramped at 80% of
|
|
15
|
+
a phone — and a breakpoint belongs in the product's stylesheet, not threaded through React state.
|
|
16
|
+
Override it on the turn, on the transcript, or in an `@media` block; the default stands alone. */
|
|
17
|
+
--xeno-message-max: 80%;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* Sides: user hugs the right rail, assistant the left. */
|
|
21
|
+
.xeno-message[data-role='user'] {
|
|
22
|
+
align-items: flex-end;
|
|
23
|
+
}
|
|
24
|
+
.xeno-message[data-role='assistant'] {
|
|
25
|
+
align-items: flex-start;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.xeno-message-body {
|
|
29
|
+
min-width: 0;
|
|
30
|
+
color: var(--xeno-text);
|
|
31
|
+
font-size: 15px;
|
|
32
|
+
line-height: 1.55;
|
|
33
|
+
overflow-wrap: anywhere;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Attachments — what was sent WITH the message, above the body and outside the bubble. They share the
|
|
37
|
+
turn's alignment and take no fill of their own: an image already has edges, and wrapping it in a
|
|
38
|
+
border plus 12px of surface only draws a second frame around the first. */
|
|
39
|
+
.xeno-message-attachments {
|
|
40
|
+
display: flex;
|
|
41
|
+
flex-wrap: wrap;
|
|
42
|
+
gap: 6px;
|
|
43
|
+
max-width: var(--xeno-message-max);
|
|
44
|
+
min-width: 0;
|
|
45
|
+
}
|
|
46
|
+
.xeno-message[data-role='user'] .xeno-message-attachments {
|
|
47
|
+
justify-content: flex-end;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* User: a bubble — surface fill, hairline border, card radius with the bottom-right corner squared. */
|
|
51
|
+
.xeno-message[data-role='user'] .xeno-message-body {
|
|
52
|
+
max-width: var(--xeno-message-max);
|
|
53
|
+
/* A person's own words keep the shape they were typed in — the blank line they left between two
|
|
54
|
+
thoughts is part of the message. Only on this side: an assistant turn is rendered prose, where
|
|
55
|
+
the source newlines are markup and honouring them would double every paragraph break. */
|
|
56
|
+
white-space: pre-wrap;
|
|
57
|
+
padding: 12px;
|
|
58
|
+
background: var(--xeno-surface);
|
|
59
|
+
border: 1px solid var(--xeno-border);
|
|
60
|
+
border-radius: var(--xeno-radius-card);
|
|
61
|
+
border-bottom-right-radius: var(--xeno-radius-xs);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Assistant: no bubble, just prose (locked design). */
|
|
65
|
+
.xeno-message[data-role='assistant'] .xeno-message-body {
|
|
66
|
+
max-width: 100%;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Meta row: timestamp + hover action bar, aligned under the turn. */
|
|
70
|
+
.xeno-message-meta {
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
gap: 8px;
|
|
74
|
+
min-height: 20px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.xeno-message-time {
|
|
78
|
+
font-size: 12px;
|
|
79
|
+
line-height: 1;
|
|
80
|
+
color: var(--xeno-muted);
|
|
81
|
+
font-variant-numeric: tabular-nums;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* Hover action bar — fades in on hover/focus of the whole turn. */
|
|
85
|
+
.xeno-message-actions {
|
|
86
|
+
display: inline-flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
gap: 2px;
|
|
89
|
+
opacity: 0;
|
|
90
|
+
/* Transparent is not gone. At `opacity: 0` these buttons still take the pointer, so the strip of
|
|
91
|
+
nothing under a message swallowed clicks and — worse — sometimes ran Copy or Regenerate on a turn
|
|
92
|
+
whose bar the reader could not see. Faded out means out of reach; hover puts it back. */
|
|
93
|
+
pointer-events: none;
|
|
94
|
+
transition: opacity 0.18s var(--xeno-ease);
|
|
95
|
+
}
|
|
96
|
+
.xeno-message:hover .xeno-message-actions,
|
|
97
|
+
.xeno-message:focus-within .xeno-message-actions {
|
|
98
|
+
opacity: 1;
|
|
99
|
+
pointer-events: auto;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* While streaming the bar stays hidden and inert, even on hover/focus (this rule wins by source order). */
|
|
103
|
+
.xeno-message[data-streaming] .xeno-message-actions {
|
|
104
|
+
opacity: 0;
|
|
105
|
+
pointer-events: none;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@media (prefers-reduced-motion: reduce) {
|
|
109
|
+
.xeno-message-actions {
|
|
110
|
+
transition: none;
|
|
111
|
+
}
|
|
112
|
+
}
|