@mistriwork/ui 0.0.0-stage → 1.0.0-rc.10
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 +21 -0
- package/README.md +50 -2
- package/dist/Avatar.d.ts +11 -0
- package/dist/Avatar.js +28 -0
- package/dist/Button.d.ts +36 -0
- package/dist/Button.js +22 -0
- package/dist/Carousel.d.ts +52 -0
- package/dist/Carousel.js +110 -0
- package/dist/Choices.d.ts +77 -0
- package/dist/Choices.js +88 -0
- package/dist/DateField.d.ts +66 -0
- package/dist/DateField.js +255 -0
- package/dist/Drawer.d.ts +33 -0
- package/dist/Drawer.js +12 -0
- package/dist/DropMenu.d.ts +59 -0
- package/dist/DropMenu.js +52 -0
- package/dist/Dropdown.d.ts +73 -0
- package/dist/Dropdown.js +85 -0
- package/dist/Field.d.ts +53 -0
- package/dist/Field.js +58 -0
- package/dist/FormParts.d.ts +68 -0
- package/dist/FormParts.js +53 -0
- package/dist/HeroBlocks.d.ts +37 -0
- package/dist/HeroBlocks.js +26 -0
- package/dist/Icon.d.ts +29 -0
- package/dist/Icon.js +78 -0
- package/dist/Modal.d.ts +48 -0
- package/dist/Modal.js +57 -0
- package/dist/Pagination.d.ts +33 -0
- package/dist/Pagination.js +25 -0
- package/dist/Popover.d.ts +21 -0
- package/dist/Popover.js +20 -0
- package/dist/Resizable.d.ts +48 -0
- package/dist/Resizable.js +122 -0
- package/dist/ScrollArea.d.ts +29 -0
- package/dist/ScrollArea.js +62 -0
- package/dist/Select.d.ts +26 -0
- package/dist/Select.js +20 -0
- package/dist/SensitiveField.d.ts +26 -0
- package/dist/SensitiveField.js +27 -0
- package/dist/Slider.d.ts +17 -0
- package/dist/Slider.js +10 -0
- package/dist/Switch.d.ts +28 -0
- package/dist/Switch.js +24 -0
- package/dist/Tabs.d.ts +25 -0
- package/dist/Tabs.js +5 -0
- package/dist/TextField.d.ts +44 -0
- package/dist/TextField.js +19 -0
- package/dist/Tooltip.d.ts +29 -0
- package/dist/Tooltip.js +39 -0
- package/dist/Typeahead.d.ts +63 -0
- package/dist/Typeahead.js +90 -0
- package/dist/fieldFrame.d.ts +37 -0
- package/dist/fieldFrame.js +16 -0
- package/dist/forms.css +192 -0
- package/dist/hero.css +23 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +40 -0
- package/dist/internal/ComponentStyleScope.d.ts +1 -0
- package/dist/internal/ComponentStyleScope.js +4 -0
- package/dist/internal/anchor.d.ts +19 -0
- package/dist/internal/anchor.js +52 -0
- package/dist/internal/datePickerModel.d.ts +53 -0
- package/dist/internal/datePickerModel.js +175 -0
- package/dist/internal/popups.d.ts +13 -0
- package/dist/internal/popups.js +16 -0
- package/dist/internal/ruler.d.ts +37 -0
- package/dist/internal/ruler.js +55 -0
- package/dist/masks.d.ts +32 -0
- package/dist/masks.js +62 -0
- package/dist/preferences.css +26 -0
- package/dist/primitives.css +188 -0
- package/dist/standalone.css +413 -0
- package/dist/style.css +19 -0
- package/dist/tokens.css +233 -0
- package/mark.svg +1 -0
- package/package.json +84 -4
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/* @mistriwork/ui's primitives, drawn: what every Button, Field, Modal, Dropdown, DateField and Icon wears wherever it is
|
|
2
|
+
used. Unlayered here, so whoever takes them names the layer (tokens.css says which). The dress a host gives them
|
|
3
|
+
beyond this — a dialog's card, a menu in a look — is the host's. */
|
|
4
|
+
|
|
5
|
+
/* The Button primitive (Button.tsx). Its intent is the dress the
|
|
6
|
+
looks already give `.primary`, `.ghost` and `.danger`; these rules are only
|
|
7
|
+
what every intent shares: one height per size, the words centred on one
|
|
8
|
+
line, and a quiet intent that is a word in the link ink rather than a box. */
|
|
9
|
+
.btn {
|
|
10
|
+
display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2h);
|
|
11
|
+
box-sizing: border-box; min-height: var(--control-h); padding: 0 var(--space-4h);
|
|
12
|
+
font-family: inherit; font-size: var(--text-base); font-weight: 500; line-height: 1.2;
|
|
13
|
+
white-space: nowrap; text-decoration: none;
|
|
14
|
+
}
|
|
15
|
+
.btn-compact { min-height: 28px; padding: 0 var(--space-3h); font-size: var(--text-sm); }
|
|
16
|
+
.btn-large { min-height: 40px; padding: 0 var(--space-5); font-size: var(--text-md); }
|
|
17
|
+
.btn-block { display: flex; width: 100%; }
|
|
18
|
+
/* A link dressed as a button (buttonClass on an <a>, where a real address
|
|
19
|
+
matters) takes the dress a page's element rules give a <button>. */
|
|
20
|
+
a.btn { border: 1px solid transparent; border-radius: var(--radius); background: var(--accent); color: var(--on-accent); cursor: pointer; }
|
|
21
|
+
a.btn.ghost { background: transparent; color: var(--ink-muted); border-color: var(--line-strong); }
|
|
22
|
+
.btn.btn-quiet { background: transparent; border-color: transparent; color: var(--link, var(--accent)); padding-inline: var(--space-3); box-shadow: none; }
|
|
23
|
+
.btn.btn-quiet:hover:not(:disabled, [aria-disabled="true"]) { background: var(--raised); }
|
|
24
|
+
.btn:disabled { cursor: not-allowed; }
|
|
25
|
+
.btn[data-busy] { cursor: progress; }
|
|
26
|
+
|
|
27
|
+
/* The Field primitive (Field.tsx): a label over its control, a
|
|
28
|
+
hint and a problem under it, all in one column. The label is text, not a
|
|
29
|
+
caption: 13px in the text ink, as the sign-in card and command forms say it, at the weight the theme's label role
|
|
30
|
+
says (`type.roles.label.weight`, worn as --type-label-weight) and 500 where it says none. */
|
|
31
|
+
.form-field { display: grid; gap: var(--space-2h); min-width: 0; align-content: start; }
|
|
32
|
+
.form-field-label { font-size: var(--text-base); font-weight: var(--type-label-weight, 500); line-height: 1.35; color: var(--ink); overflow-wrap: anywhere; }
|
|
33
|
+
/* The label's words, then the optional word and a tip after them, as one run of text: a label that wraps keeps them at
|
|
34
|
+
its end, never in a column of their own beside it. Here with the Field that draws the row, not in forms.css, which
|
|
35
|
+
loads only beside a control of its own: a form of plain boxes (Post rent, a public form) said "Month(optional)". */
|
|
36
|
+
.form-field-label-row { display: block; line-height: 1.35; }
|
|
37
|
+
.form-field-label-row > .form-field-label { display: inline; }
|
|
38
|
+
/* A word's space is a space, so a word that wraps to a line of its own starts it flush; a tip keeps its gap. */
|
|
39
|
+
.form-field-label-row > :not(.form-field-label-hidden) ~ .form-field-optional-word::before { content: " "; }
|
|
40
|
+
.form-field-label-row > :not(.form-field-label-hidden) ~ .help-tip { margin-inline-start: var(--space-2h); }
|
|
41
|
+
.form-field-label-row > .help-tip { vertical-align: -0.2em; }
|
|
42
|
+
.form-field.is-required > .form-field-label::after { content: "*" / ""; margin-left: var(--space-1h); color: var(--status-critical); }
|
|
43
|
+
.form-field-hint { margin: 0; font-size: var(--text-sm); line-height: 1.45; color: var(--ink-muted); }
|
|
44
|
+
.form-field-problem { margin: 0; font-size: var(--text-sm); line-height: 1.45; color: var(--status-critical); overflow-wrap: anywhere; }
|
|
45
|
+
.form-field-compact { gap: var(--space-2); }
|
|
46
|
+
/* A checkbox field: the box, then its label beside it, the label a click
|
|
47
|
+
target too; the hint and the problem sit under the label, not the box. */
|
|
48
|
+
.form-field-check-row { display: flex; align-items: flex-start; gap: var(--space-3); min-width: 0; }
|
|
49
|
+
.form-field-check-row > input[type="checkbox"] { flex: none; width: 16px; height: 16px; margin: 1px 0 0; accent-color: var(--accent); }
|
|
50
|
+
.form-field-check-row > .form-field-label { cursor: pointer; }
|
|
51
|
+
.form-field-check > .form-field-hint, .form-field-check > .form-field-problem { padding-left: var(--space-6); }
|
|
52
|
+
.form-field-compact .form-field-label { font-size: var(--text-sm); }
|
|
53
|
+
/* The field's control: one ground, one 3:1 edge, one height, the house ring
|
|
54
|
+
flush outside its edge, which takes the ring's ink — an inset ring on an
|
|
55
|
+
edge of the same ink was no ring at all (Mono's black on black), and a ring
|
|
56
|
+
a pixel off the edge drew a second box round the first. */
|
|
57
|
+
.field-control {
|
|
58
|
+
box-sizing: border-box; width: 100%; min-width: 0; min-height: var(--control-h); padding: var(--space-2h) var(--space-3h);
|
|
59
|
+
font: inherit; font-size: var(--text-base); line-height: 20px;
|
|
60
|
+
background: var(--field-fill); color: var(--ink);
|
|
61
|
+
border: var(--control-border-w) solid var(--line-control); border-radius: var(--field-radius, var(--radius));
|
|
62
|
+
transition: border-color var(--t-hover) var(--ease-out), background-color var(--t-hover) var(--ease-out);
|
|
63
|
+
}
|
|
64
|
+
.field-control:focus-visible { outline: var(--focus-width) solid var(--focus); outline-offset: 0; border-color: var(--focus); }
|
|
65
|
+
textarea.field-control { min-height: 72px; resize: vertical; }
|
|
66
|
+
.field-control:is(:disabled, [aria-disabled="true"]) { background: var(--raised); color: var(--ink-muted); border-color: var(--line); cursor: not-allowed; }
|
|
67
|
+
.field-control[aria-invalid="true"] { border-color: var(--status-critical); }
|
|
68
|
+
.field-control[readonly] { background: var(--raised); color: var(--ink-muted); }
|
|
69
|
+
.form-field-compact .field-control { min-height: 28px; padding: var(--space-1h) var(--space-3); font-size: var(--text-sm); }
|
|
70
|
+
|
|
71
|
+
/* What the native <dialog> gave a modal for free, said in classes: the
|
|
72
|
+
box in the middle of the viewport, sized to its content, above all
|
|
73
|
+
else; the scrim behind it. The dialogs are Base UI's now (Modal.tsx),
|
|
74
|
+
and a div has to be told. */
|
|
75
|
+
.modal {
|
|
76
|
+
position: fixed; inset: 0; margin: auto;
|
|
77
|
+
width: fit-content; height: fit-content;
|
|
78
|
+
max-width: calc(100% - 6px - 2em); max-height: calc(100% - 6px - 2em);
|
|
79
|
+
overflow: auto; z-index: 400;
|
|
80
|
+
border: solid; padding: 1em;
|
|
81
|
+
background: Canvas; color: CanvasText;
|
|
82
|
+
}
|
|
83
|
+
.modal:focus, .modal:focus-visible { outline: none; }
|
|
84
|
+
.modal-backdrop { position: fixed; inset: 0; z-index: 400; background: rgb(0 0 0 / 0.1); }
|
|
85
|
+
|
|
86
|
+
/* The themed listbox (Dropdown.tsx). */
|
|
87
|
+
.dd { position: relative; }
|
|
88
|
+
|
|
89
|
+
.dd-button {
|
|
90
|
+
width: 100%;
|
|
91
|
+
display: flex; align-items: center; justify-content: space-between; gap: var(--space-2h);
|
|
92
|
+
padding: var(--space-2) var(--space-3);
|
|
93
|
+
font: var(--text-sm) var(--mono); color: var(--ink);
|
|
94
|
+
background: var(--ground);
|
|
95
|
+
border: 1px solid var(--line-control); border-radius: var(--radius);
|
|
96
|
+
cursor: pointer;
|
|
97
|
+
}
|
|
98
|
+
/* Toward the ink, never back to a hairline: at rest the edge is already the
|
|
99
|
+
control's 3:1, so the strong border it used to take on hover would fade it. */
|
|
100
|
+
.dd-button:hover { border-color: color-mix(in srgb, var(--line-control) 60%, var(--ink)); }
|
|
101
|
+
.dd-value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
102
|
+
.dd-caret { color: var(--ink-subtle); font-size: var(--text-xs); flex: none; }
|
|
103
|
+
|
|
104
|
+
.dd-list {
|
|
105
|
+
/* Placed beside its trigger by the positioner (Base UI), and no narrower
|
|
106
|
+
than the trigger it opened from. */
|
|
107
|
+
position: relative; z-index: 40;
|
|
108
|
+
min-width: var(--anchor-width, 100%); max-width: 22rem; max-height: 18rem; overflow-y: auto;
|
|
109
|
+
background: var(--surface);
|
|
110
|
+
border: 1px solid var(--line-strong); border-radius: var(--radius-pop);
|
|
111
|
+
/* The one place a shadow earns its keep: a popover has to read as floating
|
|
112
|
+
above the page rather than punched into it. */
|
|
113
|
+
box-shadow: var(--shadow-pop);
|
|
114
|
+
padding: var(--space-2);
|
|
115
|
+
}
|
|
116
|
+
.dd-list[hidden] { display: none; }
|
|
117
|
+
|
|
118
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
119
|
+
.dd-list {
|
|
120
|
+
/* From the trigger, not the centre, so the menu reads as coming out of
|
|
121
|
+
the thing that opened it. Never scale(0) — nothing in the real world
|
|
122
|
+
appears from nothing. */
|
|
123
|
+
transform-origin: top left;
|
|
124
|
+
transition: opacity var(--t-popover) var(--ease-out), transform var(--t-popover) var(--ease-out);
|
|
125
|
+
@starting-style { opacity: 0; transform: scale(0.96) translateY(-4px); }
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.dd-option {
|
|
130
|
+
display: flex; align-items: flex-start; gap: var(--space-2h);
|
|
131
|
+
padding: var(--space-2) var(--space-3); border-radius: var(--radius-item);
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
}
|
|
134
|
+
.dd-option.active { background: var(--raised); }
|
|
135
|
+
.dd-option.selected .dd-label { color: var(--accent-ink); font-weight: 600; }
|
|
136
|
+
.dd-option.disabled { opacity: 0.5; cursor: default; }
|
|
137
|
+
.dd-check { width: 0.9em; flex: none; color: var(--accent-ink); font-size: var(--text-xs); line-height: 1.5; }
|
|
138
|
+
.dd-text { display: flex; flex-direction: column; min-width: 0; flex: 1; }
|
|
139
|
+
.dd-label { font: var(--text-sm) var(--mono); color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
140
|
+
.dd-detail { font: var(--text-xs) var(--mono); color: var(--ink-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
141
|
+
.dd-badge {
|
|
142
|
+
flex: none; align-self: center;
|
|
143
|
+
font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.05em;
|
|
144
|
+
color: var(--ink-muted); background: var(--raised);
|
|
145
|
+
border-radius: min(3px, var(--radius)); padding: 1px var(--space-2);
|
|
146
|
+
}
|
|
147
|
+
/* A tone on an option is its word's ink alone, as design.md says of a toned figure: no tint, no dot. */
|
|
148
|
+
.dd-option.dd-tone-good .dd-label { color: var(--status-good); }
|
|
149
|
+
.dd-option.dd-tone-warn .dd-label { color: var(--status-warning); }
|
|
150
|
+
.dd-option.dd-tone-bad .dd-label { color: var(--status-critical); }
|
|
151
|
+
.dd-option.dd-tone-info .dd-label { color: var(--status-info); }
|
|
152
|
+
.dd-option.dd-tone-neutral .dd-label { color: var(--ink-muted); }
|
|
153
|
+
.dd-divider { height: 1px; background: var(--line); margin: var(--space-2) var(--space-1); }
|
|
154
|
+
.dd-action .dd-label { color: var(--ink-muted); font-family: var(--sans); font-size: var(--text-sm); }
|
|
155
|
+
.dd-action:hover .dd-label { color: var(--accent-ink); }
|
|
156
|
+
|
|
157
|
+
/* The calendar field (DateField.tsx). */
|
|
158
|
+
/* A value is never clipped: the input's floor is the longest value (ten characters, sixteen with a time, in the
|
|
159
|
+
input's own font, so `ch` is its digit) plus its padding (8px, and 36px where the calendar button sits, said
|
|
160
|
+
once here), its border and a pixel of slack. The field is as narrow as that floor and no narrower, even in a
|
|
161
|
+
column that is; a cell sets its own min-width. */
|
|
162
|
+
.date-field { --date-chars: 10; display: flex; position: relative; min-width: min-content; width: 100%; }
|
|
163
|
+
.date-field.with-time { --date-chars: 16; }
|
|
164
|
+
.date-field .date-field-input { min-width: calc(var(--date-chars) * 1ch + 8px + 36px + 3px); width: 100%; padding: var(--space-2) 36px var(--space-2) var(--space-3); border: 1px solid var(--line-control); border-radius: var(--radius); background: var(--surface); color: var(--ink); font: inherit; }
|
|
165
|
+
.date-field-hint { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
|
|
166
|
+
.date-field .date-field-trigger { position: absolute; inset: 2px 2px 2px auto; width: 30px; min-height: 24px; padding: 0; display: grid; place-items: center; border: 0; background: transparent; color: var(--ink-muted); box-shadow: none; }
|
|
167
|
+
.date-field-popup { width: 294px; max-width: var(--available-width); padding: var(--space-3h); background: var(--surface); color: var(--ink); border: 1px solid var(--line-control); border-radius: var(--radius-pop); box-shadow: var(--shadow-pop); font: var(--text-base) var(--sans); }
|
|
168
|
+
.date-field-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); margin-bottom: var(--space-3); }
|
|
169
|
+
.date-field-head span { font-weight: 600; }
|
|
170
|
+
.date-field-popup button { font: inherit; color: var(--ink); background: transparent; border: 0; border-radius: var(--radius); min-height: 32px; min-width: 32px; padding: var(--space-2); box-shadow: none; }
|
|
171
|
+
.date-field-popup button:enabled:hover { background: var(--raised); }
|
|
172
|
+
.date-field-popup button:focus-visible { outline: var(--focus-width) solid var(--focus); outline-offset: -2px; }
|
|
173
|
+
.date-field-popup button:disabled { color: var(--ink-subtle); cursor: default; }
|
|
174
|
+
.date-field-popup .date-field-calendar { table-layout: fixed; width: 100%; border: 0; border-collapse: separate; border-spacing: 0; font: inherit; }
|
|
175
|
+
.date-field-calendar th { color: var(--ink-muted); font-size: var(--text-xs); font-weight: 400; }
|
|
176
|
+
.date-field-calendar button { width: 100%; }
|
|
177
|
+
.date-field-calendar button[data-outside] { color: var(--ink-muted); }
|
|
178
|
+
.date-field-calendar button[aria-current="date"] { text-decoration: underline; text-underline-offset: 4px; }
|
|
179
|
+
.date-field-calendar button[data-selected] { background: var(--accent-soft); color: var(--accent-ink); font-weight: 600; }
|
|
180
|
+
.date-field-foot { display: flex; justify-content: space-between; border-top: 1px solid var(--line); margin-top: var(--space-3); padding-top: var(--space-2h); }
|
|
181
|
+
@media (pointer: coarse) { .date-field-popup button { min-height: 36px; } }
|
|
182
|
+
|
|
183
|
+
/* An icon (Icon.tsx). */
|
|
184
|
+
.tw-icon { display: inline-block; flex: none; vertical-align: middle; }
|
|
185
|
+
|
|
186
|
+
/* The newer standalone primitives' rules live in standalone.css, not
|
|
187
|
+
here: the kit imports this sheet into every first paint, and none of
|
|
188
|
+
them is drawn by the kit. */
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
/* The newer standalone primitives' rules (Switch, Tabs, Drawer, Popover,
|
|
2
|
+
Avatar, Pagination), kept apart from primitives.css on purpose: the
|
|
3
|
+
kit's stylesheet imports primitives.css into every first paint, and
|
|
4
|
+
none of these is drawn by the kit, so their rules would be dead
|
|
5
|
+
weight against the bundle budget there. A page drawn with the
|
|
6
|
+
primitives alone gets them through style.css, which imports this
|
|
7
|
+
sheet; the kit does not. */
|
|
8
|
+
|
|
9
|
+
/* The Switch primitive (Switch.tsx): a 48x28 track, a 24px thumb, the
|
|
10
|
+
accent when on, the line ink when off; the thumb moves, the track
|
|
11
|
+
never changes size, so the row beside it never shifts. */
|
|
12
|
+
.ui-switch-row {
|
|
13
|
+
display: inline-flex;
|
|
14
|
+
align-items: flex-start;
|
|
15
|
+
gap: var(--space-3);
|
|
16
|
+
min-width: 0;
|
|
17
|
+
}
|
|
18
|
+
.ui-switch {
|
|
19
|
+
flex: none;
|
|
20
|
+
width: 48px;
|
|
21
|
+
height: 28px;
|
|
22
|
+
padding: var(--space-1);
|
|
23
|
+
border: 1px solid var(--line-control);
|
|
24
|
+
border-radius: 999px;
|
|
25
|
+
background: var(--raised);
|
|
26
|
+
cursor: pointer;
|
|
27
|
+
box-sizing: border-box;
|
|
28
|
+
}
|
|
29
|
+
.ui-switch[data-checked] {
|
|
30
|
+
background: var(--accent);
|
|
31
|
+
border-color: var(--accent);
|
|
32
|
+
}
|
|
33
|
+
.ui-switch:disabled {
|
|
34
|
+
opacity: 0.5;
|
|
35
|
+
cursor: not-allowed;
|
|
36
|
+
}
|
|
37
|
+
.ui-switch:focus-visible {
|
|
38
|
+
outline: var(--focus-width) solid var(--focus);
|
|
39
|
+
outline-offset: 2px;
|
|
40
|
+
}
|
|
41
|
+
.ui-switch-thumb {
|
|
42
|
+
display: block;
|
|
43
|
+
width: 22px;
|
|
44
|
+
height: 22px;
|
|
45
|
+
border-radius: 50%;
|
|
46
|
+
background: var(--surface);
|
|
47
|
+
/* No shadow: a literal here would need a reviewed baseline entry, and
|
|
48
|
+
the shadow tokens are all sized for popups, not a 22px thumb. */
|
|
49
|
+
transition: transform 150ms ease;
|
|
50
|
+
}
|
|
51
|
+
.ui-switch[data-checked] .ui-switch-thumb {
|
|
52
|
+
transform: translateX(20px);
|
|
53
|
+
}
|
|
54
|
+
.ui-switch-copy {
|
|
55
|
+
display: grid;
|
|
56
|
+
gap: var(--space-1);
|
|
57
|
+
min-width: 0;
|
|
58
|
+
}
|
|
59
|
+
.ui-switch-label {
|
|
60
|
+
font: var(--text-base) var(--sans);
|
|
61
|
+
color: var(--ink);
|
|
62
|
+
cursor: pointer;
|
|
63
|
+
}
|
|
64
|
+
.ui-switch-description {
|
|
65
|
+
font-size: var(--text-sm);
|
|
66
|
+
line-height: 1.45;
|
|
67
|
+
color: var(--ink-muted);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* The Tabs primitive (Tabs.tsx): a row of tabs on a rule, the chosen
|
|
71
|
+
one in the accent ink with the accent's underline, the panel below. */
|
|
72
|
+
.ui-tabs-list {
|
|
73
|
+
display: flex;
|
|
74
|
+
gap: var(--space-1);
|
|
75
|
+
border-bottom: 1px solid var(--line);
|
|
76
|
+
overflow-x: auto;
|
|
77
|
+
}
|
|
78
|
+
.ui-tab {
|
|
79
|
+
flex: none;
|
|
80
|
+
padding: var(--space-2h) var(--space-3h);
|
|
81
|
+
border: 0;
|
|
82
|
+
border-bottom: 2px solid transparent;
|
|
83
|
+
background: transparent;
|
|
84
|
+
color: var(--ink-muted);
|
|
85
|
+
font: inherit;
|
|
86
|
+
cursor: pointer;
|
|
87
|
+
white-space: nowrap;
|
|
88
|
+
}
|
|
89
|
+
@media (hover: hover) {
|
|
90
|
+
.ui-tab:hover {
|
|
91
|
+
color: var(--ink);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
.ui-tab[data-selected] {
|
|
95
|
+
color: var(--accent-ink);
|
|
96
|
+
border-bottom-color: var(--accent);
|
|
97
|
+
font-weight: 600;
|
|
98
|
+
}
|
|
99
|
+
.ui-tab:disabled {
|
|
100
|
+
color: var(--ink-subtle);
|
|
101
|
+
cursor: not-allowed;
|
|
102
|
+
}
|
|
103
|
+
.ui-tab:focus-visible {
|
|
104
|
+
outline: var(--focus-width) solid var(--focus);
|
|
105
|
+
outline-offset: -2px;
|
|
106
|
+
}
|
|
107
|
+
.ui-tab-panel {
|
|
108
|
+
padding-top: var(--space-4);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* The Drawer primitive (Drawer.tsx): a sheet from an edge, at most
|
|
112
|
+
420px, full width on a phone, on the modal's scrim. */
|
|
113
|
+
.ui-drawer-backdrop {
|
|
114
|
+
background: var(--scrim);
|
|
115
|
+
}
|
|
116
|
+
.ui-drawer-viewport {
|
|
117
|
+
position: fixed;
|
|
118
|
+
inset: 0;
|
|
119
|
+
display: flex;
|
|
120
|
+
}
|
|
121
|
+
.ui-drawer-right {
|
|
122
|
+
justify-content: flex-end;
|
|
123
|
+
}
|
|
124
|
+
.ui-drawer-left {
|
|
125
|
+
justify-content: flex-start;
|
|
126
|
+
}
|
|
127
|
+
.ui-drawer {
|
|
128
|
+
width: min(420px, 100vw);
|
|
129
|
+
max-height: 100dvh;
|
|
130
|
+
overflow-y: auto;
|
|
131
|
+
padding: var(--space-5);
|
|
132
|
+
background: var(--surface);
|
|
133
|
+
color: var(--ink);
|
|
134
|
+
box-shadow: var(--shadow-pop);
|
|
135
|
+
}
|
|
136
|
+
.ui-drawer-title {
|
|
137
|
+
margin: 0 0 var(--space-4);
|
|
138
|
+
font-size: var(--text-md);
|
|
139
|
+
font-weight: 600;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* The Popover primitive (Popover.tsx): a layer on the surface, with
|
|
143
|
+
the popup's edge and shadow, never wider than the viewport. */
|
|
144
|
+
.ui-popover {
|
|
145
|
+
max-width: min(28rem, calc(100vw - 16px));
|
|
146
|
+
padding: var(--space-3h);
|
|
147
|
+
background: var(--surface);
|
|
148
|
+
color: var(--ink);
|
|
149
|
+
border: 1px solid var(--line-control);
|
|
150
|
+
border-radius: var(--radius-pop);
|
|
151
|
+
box-shadow: var(--shadow-pop);
|
|
152
|
+
}
|
|
153
|
+
.ui-popover:focus-visible {
|
|
154
|
+
outline: var(--focus-width) solid var(--focus);
|
|
155
|
+
outline-offset: -2px;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/* The Avatar primitive (Avatar.tsx): a disc in the accent's tint, the
|
|
159
|
+
initials in the accent ink, the picture covering it whole. */
|
|
160
|
+
.ui-avatar {
|
|
161
|
+
display: inline-grid;
|
|
162
|
+
place-items: center;
|
|
163
|
+
flex: none;
|
|
164
|
+
border-radius: 50%;
|
|
165
|
+
overflow: hidden;
|
|
166
|
+
background: var(--accent-soft);
|
|
167
|
+
color: var(--accent-ink);
|
|
168
|
+
font-weight: 600;
|
|
169
|
+
}
|
|
170
|
+
.ui-avatar-sm {
|
|
171
|
+
width: 28px;
|
|
172
|
+
height: 28px;
|
|
173
|
+
font-size: var(--text-xs);
|
|
174
|
+
}
|
|
175
|
+
.ui-avatar-md {
|
|
176
|
+
width: 36px;
|
|
177
|
+
height: 36px;
|
|
178
|
+
font-size: var(--text-sm);
|
|
179
|
+
}
|
|
180
|
+
.ui-avatar-lg {
|
|
181
|
+
width: 48px;
|
|
182
|
+
height: 48px;
|
|
183
|
+
font-size: var(--text-base);
|
|
184
|
+
}
|
|
185
|
+
.ui-avatar-image {
|
|
186
|
+
width: 100%;
|
|
187
|
+
height: 100%;
|
|
188
|
+
object-fit: cover;
|
|
189
|
+
}
|
|
190
|
+
.ui-avatar-fallback {
|
|
191
|
+
display: grid;
|
|
192
|
+
place-items: center;
|
|
193
|
+
width: 100%;
|
|
194
|
+
height: 100%;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* The Pagination primitive (Pagination.tsx): page buttons on one
|
|
198
|
+
row, the current page in the accent's tint, steps disabled at the ends. */
|
|
199
|
+
.ui-pagination {
|
|
200
|
+
display: flex;
|
|
201
|
+
align-items: center;
|
|
202
|
+
gap: var(--space-1h);
|
|
203
|
+
flex-wrap: wrap;
|
|
204
|
+
}
|
|
205
|
+
.ui-page {
|
|
206
|
+
min-width: 36px;
|
|
207
|
+
min-height: 36px;
|
|
208
|
+
padding: 0 var(--space-2h);
|
|
209
|
+
border: 1px solid transparent;
|
|
210
|
+
border-radius: var(--radius);
|
|
211
|
+
background: transparent;
|
|
212
|
+
color: var(--ink);
|
|
213
|
+
font: inherit;
|
|
214
|
+
cursor: pointer;
|
|
215
|
+
}
|
|
216
|
+
@media (hover: hover) {
|
|
217
|
+
.ui-page:hover:not(:disabled) {
|
|
218
|
+
background: var(--raised);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
.ui-page[aria-current='page'] {
|
|
222
|
+
background: var(--accent-soft);
|
|
223
|
+
color: var(--accent-ink);
|
|
224
|
+
font-weight: 600;
|
|
225
|
+
}
|
|
226
|
+
.ui-page:disabled {
|
|
227
|
+
color: var(--ink-subtle);
|
|
228
|
+
cursor: not-allowed;
|
|
229
|
+
}
|
|
230
|
+
.ui-page:focus-visible {
|
|
231
|
+
outline: var(--focus-width) solid var(--focus);
|
|
232
|
+
outline-offset: 1px;
|
|
233
|
+
}
|
|
234
|
+
.ui-page-gap {
|
|
235
|
+
padding: 0 var(--space-1);
|
|
236
|
+
color: var(--ink-subtle);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/* The Carousel primitive (Carousel.tsx): one slide in a viewport, the
|
|
240
|
+
rest off to the side on a track that slides — only under
|
|
241
|
+
`no-preference`, so a reader who asked for stillness gets a cut. */
|
|
242
|
+
.ui-carousel {
|
|
243
|
+
display: grid;
|
|
244
|
+
gap: var(--space-3);
|
|
245
|
+
min-width: 0;
|
|
246
|
+
}
|
|
247
|
+
.ui-carousel-viewport {
|
|
248
|
+
overflow: hidden;
|
|
249
|
+
min-width: 0;
|
|
250
|
+
}
|
|
251
|
+
.ui-carousel-track {
|
|
252
|
+
display: flex;
|
|
253
|
+
}
|
|
254
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
255
|
+
.ui-carousel-track {
|
|
256
|
+
transition: transform var(--t-panel) var(--ease-out);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
.ui-carousel-slide {
|
|
260
|
+
flex: 0 0 100%;
|
|
261
|
+
min-width: 0;
|
|
262
|
+
}
|
|
263
|
+
.ui-carousel-controls {
|
|
264
|
+
display: flex;
|
|
265
|
+
align-items: center;
|
|
266
|
+
justify-content: space-between;
|
|
267
|
+
gap: var(--space-3);
|
|
268
|
+
}
|
|
269
|
+
.ui-carousel-step {
|
|
270
|
+
padding: var(--space-2) var(--space-3);
|
|
271
|
+
border: 1px solid var(--line-control);
|
|
272
|
+
border-radius: var(--radius);
|
|
273
|
+
background: transparent;
|
|
274
|
+
color: var(--ink);
|
|
275
|
+
font: inherit;
|
|
276
|
+
cursor: pointer;
|
|
277
|
+
}
|
|
278
|
+
@media (hover: hover) {
|
|
279
|
+
.ui-carousel-step:hover:not(:disabled) {
|
|
280
|
+
background: var(--raised);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
.ui-carousel-step:disabled {
|
|
284
|
+
color: var(--ink-subtle);
|
|
285
|
+
cursor: not-allowed;
|
|
286
|
+
}
|
|
287
|
+
.ui-carousel-step:focus-visible,
|
|
288
|
+
.ui-carousel-dot:focus-visible {
|
|
289
|
+
outline: var(--focus-width) solid var(--focus);
|
|
290
|
+
outline-offset: 2px;
|
|
291
|
+
}
|
|
292
|
+
.ui-carousel-dots {
|
|
293
|
+
display: flex;
|
|
294
|
+
gap: var(--space-2);
|
|
295
|
+
}
|
|
296
|
+
.ui-carousel-dot {
|
|
297
|
+
width: var(--space-4);
|
|
298
|
+
height: var(--space-4);
|
|
299
|
+
padding: 0;
|
|
300
|
+
border: 0;
|
|
301
|
+
border-radius: 50%;
|
|
302
|
+
background: var(--line-control);
|
|
303
|
+
cursor: pointer;
|
|
304
|
+
}
|
|
305
|
+
.ui-carousel-dot[aria-current] {
|
|
306
|
+
background: var(--accent);
|
|
307
|
+
}
|
|
308
|
+
.ui-carousel-status {
|
|
309
|
+
margin: 0;
|
|
310
|
+
font-size: var(--text-sm);
|
|
311
|
+
color: var(--ink-muted);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/* The Resizable primitive (Resizable.tsx): panes on a flex row (or
|
|
315
|
+
column), a handle between each pair that the reader drags or walks. */
|
|
316
|
+
.ui-resizable-horizontal {
|
|
317
|
+
display: flex;
|
|
318
|
+
flex-direction: row;
|
|
319
|
+
min-width: 0;
|
|
320
|
+
}
|
|
321
|
+
.ui-resizable-vertical {
|
|
322
|
+
display: flex;
|
|
323
|
+
flex-direction: column;
|
|
324
|
+
min-height: 0;
|
|
325
|
+
}
|
|
326
|
+
.ui-resizable-panel {
|
|
327
|
+
flex: 0 0 auto;
|
|
328
|
+
min-width: 0;
|
|
329
|
+
min-height: 0;
|
|
330
|
+
overflow: auto;
|
|
331
|
+
}
|
|
332
|
+
.ui-resizable-handle {
|
|
333
|
+
flex: none;
|
|
334
|
+
background: var(--raised);
|
|
335
|
+
border: 1px solid var(--line);
|
|
336
|
+
/* A touch drag resizes, never scrolls the page: without this the
|
|
337
|
+
browser takes the gesture for scrolling before the pointer handler
|
|
338
|
+
sees it. */
|
|
339
|
+
touch-action: none;
|
|
340
|
+
}
|
|
341
|
+
.ui-resizable-horizontal .ui-resizable-handle {
|
|
342
|
+
width: var(--space-4);
|
|
343
|
+
cursor: col-resize;
|
|
344
|
+
}
|
|
345
|
+
.ui-resizable-vertical .ui-resizable-handle {
|
|
346
|
+
height: var(--space-4);
|
|
347
|
+
cursor: row-resize;
|
|
348
|
+
}
|
|
349
|
+
@media (hover: hover) {
|
|
350
|
+
.ui-resizable-handle:hover {
|
|
351
|
+
background: var(--line-control);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
.ui-resizable-handle:focus-visible {
|
|
355
|
+
outline: var(--focus-width) solid var(--focus);
|
|
356
|
+
outline-offset: 2px;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/* The ScrollArea primitive (ScrollArea.tsx): Base UI draws no chrome of
|
|
360
|
+
its own, so the viewport hides the native bar and the thumb below is
|
|
361
|
+
the bar the reader drags. */
|
|
362
|
+
.ui-scroll-area {
|
|
363
|
+
position: relative;
|
|
364
|
+
min-width: 0;
|
|
365
|
+
}
|
|
366
|
+
.ui-scroll-area-viewport {
|
|
367
|
+
overflow: auto;
|
|
368
|
+
scrollbar-width: none;
|
|
369
|
+
}
|
|
370
|
+
.ui-scroll-area-viewport::-webkit-scrollbar {
|
|
371
|
+
display: none;
|
|
372
|
+
}
|
|
373
|
+
.ui-scroll-area-viewport:focus-visible {
|
|
374
|
+
outline: var(--focus-width) solid var(--focus);
|
|
375
|
+
outline-offset: 2px;
|
|
376
|
+
}
|
|
377
|
+
.ui-scroll-area-scrollbar {
|
|
378
|
+
position: absolute;
|
|
379
|
+
display: flex;
|
|
380
|
+
background: transparent;
|
|
381
|
+
}
|
|
382
|
+
.ui-scroll-area-scrollbar-vertical {
|
|
383
|
+
top: 0;
|
|
384
|
+
right: 0;
|
|
385
|
+
bottom: 0;
|
|
386
|
+
width: var(--space-3);
|
|
387
|
+
}
|
|
388
|
+
.ui-scroll-area-scrollbar-horizontal {
|
|
389
|
+
left: 0;
|
|
390
|
+
right: 0;
|
|
391
|
+
bottom: 0;
|
|
392
|
+
height: var(--space-3);
|
|
393
|
+
}
|
|
394
|
+
.ui-scroll-area-thumb {
|
|
395
|
+
flex: 1;
|
|
396
|
+
background: var(--line-control);
|
|
397
|
+
border-radius: var(--radius);
|
|
398
|
+
}
|
|
399
|
+
.ui-scroll-area-corner {
|
|
400
|
+
background: var(--surface);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/* The Tooltip primitive (Tooltip.tsx): a short note on the ink, never a
|
|
404
|
+
second control — the trigger keeps its own look, so it has no rule. */
|
|
405
|
+
.ui-tooltip {
|
|
406
|
+
max-width: min(28rem, calc(100vw - 16px));
|
|
407
|
+
padding: var(--space-2) var(--space-3);
|
|
408
|
+
background: var(--ink);
|
|
409
|
+
color: var(--surface);
|
|
410
|
+
border-radius: var(--radius);
|
|
411
|
+
font-size: var(--text-sm);
|
|
412
|
+
line-height: 1.45;
|
|
413
|
+
}
|
package/dist/style.css
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/* @mistriwork/ui's stylesheet, for a page drawn with the primitives alone: the tokens, the primitives' rules, and the
|
|
2
|
+
dress a page's own element rules would give a Button. All of it stands in the `mistri-ui` layer, so any rule of the
|
|
3
|
+
page's own — in a later layer or in none — wins over it. The kit's stylesheet takes tokens.css and primitives.css
|
|
4
|
+
into its own base layer instead (packages/kit/src/client/style.css says why) and dresses the button itself; the form
|
|
5
|
+
controls' rules (forms.css) and the hero blocks' (hero.css) wait there for the first control a page draws. */
|
|
6
|
+
@layer mistri-ui;
|
|
7
|
+
@import './tokens.css' layer(mistri-ui);
|
|
8
|
+
@import './primitives.css' layer(mistri-ui);
|
|
9
|
+
@import './standalone.css' layer(mistri-ui);
|
|
10
|
+
@import './forms.css' layer(mistri-ui);
|
|
11
|
+
@import './hero.css' layer(mistri-ui);
|
|
12
|
+
@import './preferences.css' layer(mistri-ui);
|
|
13
|
+
|
|
14
|
+
@layer mistri-ui {
|
|
15
|
+
.btn { border: 1px solid transparent; border-radius: var(--radius); background: var(--accent); color: var(--on-accent); cursor: pointer; }
|
|
16
|
+
.btn.btn-secondary { background: transparent; color: var(--ink-muted); border-color: var(--line-strong); }
|
|
17
|
+
.btn.btn-danger { background: var(--status-critical); color: var(--surface); border-color: var(--status-critical); }
|
|
18
|
+
.btn:focus-visible { outline: var(--focus-width) solid var(--focus); outline-offset: var(--focus-offset); }
|
|
19
|
+
}
|