@apliteni/apliteni-ui 0.11.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/package.json +1 -1
- package/react/dist/index.css +0 -1
- package/src/assets/icons.js +67 -4
- package/src/components/index.js +23 -3
- package/src/styles/callout.css +30 -11
package/README.md
CHANGED
|
@@ -31,6 +31,12 @@ as the vanilla kit, so the two layers can't drift.
|
|
|
31
31
|
**Which one:** does the surface hold meaningful client state? No → the HTML-string
|
|
32
32
|
factories below. Yes → the [React components](#react-components-stateful-surfaces).
|
|
33
33
|
|
|
34
|
+
Either layer follows the same UI rules — which component to reach for, the states it
|
|
35
|
+
owes, how colour and wording work. They live in the **Guidelines** section of Storybook,
|
|
36
|
+
which opens on
|
|
37
|
+
[an overview of the five pages](https://ui.apli.tech/storybook/?path=/story/guidelines-overview--overview)
|
|
38
|
+
and what the kit does and does not yet meet. Worth reading before you design a screen.
|
|
39
|
+
|
|
34
40
|
## Install
|
|
35
41
|
|
|
36
42
|
Published on the **public npm registry** — no scope config, no token:
|
package/package.json
CHANGED
package/react/dist/index.css
CHANGED
package/src/assets/icons.js
CHANGED
|
@@ -6,7 +6,29 @@
|
|
|
6
6
|
//
|
|
7
7
|
// Each value is the INNER markup; icon() wraps it in the shared <svg>. Glyphs
|
|
8
8
|
// are grouped by domain — the flat ICONS map is what icon() looks up, and
|
|
9
|
-
// iconCategories drives the Storybook grid.
|
|
9
|
+
// iconCategories drives the Storybook grid.
|
|
10
|
+
//
|
|
11
|
+
// ---- Adding a glyph -------------------------------------------------------
|
|
12
|
+
//
|
|
13
|
+
// NAME it for what it depicts, not for the one place it is used: `trash`, not
|
|
14
|
+
// `deleteWorkspace`. camelCase, and a modifier follows its noun — `circleX`,
|
|
15
|
+
// `eyeOff`, `trendingUp` — so the family sorts together. A name is taken once;
|
|
16
|
+
// a second declaration of one is a gate failure, not a merge conflict.
|
|
17
|
+
//
|
|
18
|
+
// GROUP it by what it depicts, again rather than by caller. `chart` lives in
|
|
19
|
+
// DATA because it draws data, even when a comms panel is what renders it. If
|
|
20
|
+
// two groups both look right, the glyph belongs to the one whose other members
|
|
21
|
+
// it would sit beside in the catalogue. Groups are not tags: exactly one.
|
|
22
|
+
//
|
|
23
|
+
// PROVENANCE: the path comes from Lucide, unmodified, at the 24×24 / 1.7 house
|
|
24
|
+
// stroke — that is what keeps the set looking like one hand. Say which Lucide
|
|
25
|
+
// name it came from in the commit if the two differ. A hand-drawn path needs a
|
|
26
|
+
// reason in the commit message, because the next person cannot tell one from a
|
|
27
|
+
// traced one by looking.
|
|
28
|
+
//
|
|
29
|
+
// The gates: src/assets/icons.test.js holds one-group-per-glyph and the
|
|
30
|
+
// emitter's numbers, and stories/guidelines/iconography.test.js holds the
|
|
31
|
+
// icon-only list below. The written rules are on Guidelines / Iconography.
|
|
10
32
|
|
|
11
33
|
const NAV = {
|
|
12
34
|
chevronDown: '<path d="M6 9l6 6 6-6" stroke-linecap="round" stroke-linejoin="round"/>',
|
|
@@ -101,9 +123,6 @@ const COMMS = {
|
|
|
101
123
|
logout: '<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/>',
|
|
102
124
|
plug: '<path d="M12 22v-5"/><path d="M15 8V2"/><path d="M17 8a1 1 0 0 1 1 1v4a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1z"/><path d="M9 8V2"/>',
|
|
103
125
|
sparkle: '<path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/><path d="M20 2v4"/><path d="M22 4h-4"/><circle cx="4" cy="20" r="2"/>',
|
|
104
|
-
card: '<rect x="1" y="4" width="22" height="16" rx="2" ry="2"/><line x1="1" y1="10" x2="23" y2="10"/>',
|
|
105
|
-
doc: '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/>',
|
|
106
|
-
chart: '<line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/>',
|
|
107
126
|
};
|
|
108
127
|
|
|
109
128
|
const BRAND = {
|
|
@@ -140,5 +159,49 @@ export const icon = (name, cls = '') =>
|
|
|
140
159
|
|
|
141
160
|
export const iconNames = Object.keys(ICONS);
|
|
142
161
|
|
|
162
|
+
// Which controls may be an icon and nothing else — a closed list, not a
|
|
163
|
+
// judgement call in review.
|
|
164
|
+
//
|
|
165
|
+
// The accessibility half was already held: every glyph is aria-hidden, so
|
|
166
|
+
// button({ iconOnly }) names itself from `label`. That says a nameless icon
|
|
167
|
+
// button cannot ship; it never said when a wordless one SHOULD. This does.
|
|
168
|
+
//
|
|
169
|
+
// A control whose action is on this list may drop its visible text. Everything
|
|
170
|
+
// else carries a label — including glyphs that feel obvious in isolation, like
|
|
171
|
+
// `gear` or `externalLink`, because a reader meets them one at a time and a
|
|
172
|
+
// toolbar is not a legend. Adding an entry is a decision recorded here, which
|
|
173
|
+
// is the point: the alternative rules read well and could not be gated.
|
|
174
|
+
export const iconOnlyAllowed = {
|
|
175
|
+
x: 'close or dismiss',
|
|
176
|
+
copy: 'copy to clipboard',
|
|
177
|
+
moreHorizontal: 'overflow menu',
|
|
178
|
+
moreVertical: 'overflow menu',
|
|
179
|
+
chevronDown: 'expand or collapse',
|
|
180
|
+
chevronUp: 'expand or collapse',
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// What a glyph means when a component picks it for the reader rather than a
|
|
184
|
+
// caller naming it. Two rules hold the map together:
|
|
185
|
+
//
|
|
186
|
+
// a CIRCLED glyph is a state the system is in — circleCheck, circleX,
|
|
187
|
+
// circleAlert. It is reported to you and you cannot click it.
|
|
188
|
+
// a BARE glyph is an action you can take — x dismisses, check confirms,
|
|
189
|
+
// trash deletes.
|
|
190
|
+
//
|
|
191
|
+
// That split is what a danger toast needed: it used to render the same bare `x`
|
|
192
|
+
// twice, once meaning "this failed" and once meaning "make this go away". The
|
|
193
|
+
// bare x is now reserved for the close button, and status took the circle
|
|
194
|
+
// family the kit was already shipping and never using.
|
|
195
|
+
export const iconMeanings = {
|
|
196
|
+
circleCheck: 'a state: it succeeded',
|
|
197
|
+
circleX: 'a state: it failed',
|
|
198
|
+
circleAlert: 'a state: it needs attention',
|
|
199
|
+
info: 'a state: something worth knowing, no action required',
|
|
200
|
+
bolt: 'a state: it happened, with no verdict attached',
|
|
201
|
+
x: 'an action: close or dismiss this',
|
|
202
|
+
check: 'an action: confirm this',
|
|
203
|
+
trash: 'an action: delete this',
|
|
204
|
+
};
|
|
205
|
+
|
|
143
206
|
export const sun = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>';
|
|
144
207
|
export const moon = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg>';
|
package/src/components/index.js
CHANGED
|
@@ -16,6 +16,10 @@ export const esc = (s) => String(s == null ? '' : s).replace(/[&<>"]/g, (c) => (
|
|
|
16
16
|
// stops being decoration and becomes the control's ONLY accessible name. It is
|
|
17
17
|
// mirrored into aria-label + title, and an empty label falls back to the icon
|
|
18
18
|
// name rather than shipping a nameless button.
|
|
19
|
+
//
|
|
20
|
+
// That is the accessibility half, and it is not permission. WHEN a control may
|
|
21
|
+
// go wordless is a closed list — `iconOnlyAllowed` in src/assets/icons.js — and
|
|
22
|
+
// stories/guidelines/iconography.test.js reviews call sites against it.
|
|
19
23
|
export function button({
|
|
20
24
|
label = 'Button', variant = 'secondary', size = 'md', icon: ic, iconSvg, iconRight,
|
|
21
25
|
block = false, disabled = false, busy = false, type = 'button', href, iconOnly = false,
|
|
@@ -93,8 +97,14 @@ export function segmented({ options = [], active = 0, size, block, name = 'seg',
|
|
|
93
97
|
}
|
|
94
98
|
|
|
95
99
|
// ---- Accent picker -------------------------------------------------------
|
|
100
|
+
// Each swatch is made of the tokens its accent selects: it fades from the next
|
|
101
|
+
// distinct step up that accent's dark ramp down to dark --accent. Most accents
|
|
102
|
+
// land on --purple-light; Nebula's is its own --accent since #157, so it walks
|
|
103
|
+
// on to --purple-mid. site/chrome.mjs and site/index.html hand-keep the same
|
|
104
|
+
// four strings and must change with these. Held by stories/accent-swatch.test.js,
|
|
105
|
+
// which derives both sides rather than restating them. See issue #190.
|
|
96
106
|
const ACCENT_SWATCH = {
|
|
97
|
-
default: 'linear-gradient(135deg,#
|
|
107
|
+
default: 'linear-gradient(135deg,#bd8cff,#b479ff)',
|
|
98
108
|
phoenix: 'linear-gradient(135deg,#ff8a5c,#ff6a3d)',
|
|
99
109
|
ocean: 'linear-gradient(135deg,#5ab0ff,#3b9dff)',
|
|
100
110
|
emerald: 'linear-gradient(135deg,#3ad9a0,#16c98a)',
|
|
@@ -211,8 +221,18 @@ export function switchToggle({ checked = false, disabled = false, name, label =
|
|
|
211
221
|
export function callout({ variant, icon: ic = 'info', body } = {}) {
|
|
212
222
|
return `<div class="${cx('ui-callout', variant && `ui-callout--${variant}`)}"><span class="ui-callout__icon">${icon(ic)}</span><div>${body}</div></div>`;
|
|
213
223
|
}
|
|
214
|
-
// Default icon per status — overridable with `icon`.
|
|
215
|
-
|
|
224
|
+
// Default icon per status — overridable with `icon`. The circle family is not
|
|
225
|
+
// decoration: a circled glyph is a STATE the system reports, a bare one is an
|
|
226
|
+
// ACTION you can take (iconMeanings in src/assets/icons.js).
|
|
227
|
+
//
|
|
228
|
+
// This used to read `success: 'check', danger: 'x', warn: 'alert'`, which made
|
|
229
|
+
// a danger toast render the same bare `x` twice — once as the status, meaning
|
|
230
|
+
// "this failed", once as the close button, meaning "make this go away". Status
|
|
231
|
+
// took the circle glyphs the kit already shipped and never used, and the bare
|
|
232
|
+
// `x` now belongs to the close button alone.
|
|
233
|
+
const TOAST_ICON = {
|
|
234
|
+
success: 'circleCheck', danger: 'circleX', warn: 'circleAlert', info: 'info', neutral: 'bolt',
|
|
235
|
+
};
|
|
216
236
|
// A toast carries a status (colour) and a style (surface). Everything visual is
|
|
217
237
|
// token-driven: the status modifier sets --toast-accent/-glow/-on, the style
|
|
218
238
|
// modifier consumes them. `action` adds a trailing button ("Undo"/"Retry"),
|
package/src/styles/callout.css
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
/* ----------------------------------------------------------------------------
|
|
30
30
|
* Toast — a floating, dismissible notification.
|
|
31
31
|
* A status (colour) × style (surface) matrix, entirely token-driven:
|
|
32
|
-
* status success · danger · warn · info · neutral → sets --toast-accent/-glow/-on/-solid
|
|
32
|
+
* status success · danger · warn · info · neutral → sets --toast-accent/-action-ink/-glow/-on/-solid
|
|
33
33
|
* style soft (tinted) · solid (filled) · outline (bordered)
|
|
34
34
|
* -------------------------------------------------------------------------- */
|
|
35
35
|
.ui-toast {
|
|
@@ -51,12 +51,21 @@
|
|
|
51
51
|
--toast-accent is the status as a line or a small mark, --toast-glow its wash,
|
|
52
52
|
--toast-on the glyph ink on the accent circle. --toast-solid is separate on
|
|
53
53
|
purpose: a full fill is a different job from a 22px circle, and it is chosen
|
|
54
|
-
as a PAIR with --signal-solid-ink rather than against a global. See #149.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
as a PAIR with --signal-solid-ink rather than against a global. See #149.
|
|
55
|
+
|
|
56
|
+
--toast-action-ink is separate from --toast-accent for the same reason the
|
|
57
|
+
fill is: the accent is a graphic colour, sized for a 3px rule and a 22px
|
|
58
|
+
circle, and in the light theme it is not a colour text can be set in. The
|
|
59
|
+
trailing action is the one part of a toast that is BOTH the status colour and
|
|
60
|
+
a piece of text, so it takes the chip inks, which are the text-grade version
|
|
61
|
+
of the same five statuses. In the dark theme the two are the same value by
|
|
62
|
+
construction — --chip-success-ink IS --green there — so this splits the light
|
|
63
|
+
theme apart and leaves the dark one exactly as it was. */
|
|
64
|
+
.ui-toast--success { --toast-accent: var(--green); --toast-action-ink: var(--chip-success-ink); --toast-glow: var(--glow-green); --toast-on: var(--signal-contrast); --toast-solid: var(--signal-solid-success); }
|
|
65
|
+
.ui-toast--danger { --toast-accent: var(--pink); --toast-action-ink: var(--chip-danger-ink); --toast-glow: var(--glow-pink); --toast-on: var(--danger-contrast); --toast-solid: var(--signal-solid-danger); }
|
|
66
|
+
.ui-toast--warn { --toast-accent: var(--amber); --toast-action-ink: var(--chip-warn-ink); --toast-glow: color-mix(in srgb, var(--amber) 14%, transparent); --toast-on: var(--signal-contrast); --toast-solid: var(--signal-solid-warn); }
|
|
67
|
+
.ui-toast--info { --toast-accent: var(--cyan); --toast-action-ink: var(--chip-info-ink); --toast-glow: var(--glow-cyan); --toast-on: var(--signal-contrast); --toast-solid: var(--signal-solid-info); }
|
|
68
|
+
.ui-toast--neutral { --toast-accent: var(--muted); --toast-action-ink: var(--muted); --toast-glow: var(--surface-2); --toast-on: var(--strong); --toast-solid: var(--signal-solid-neutral); }
|
|
60
69
|
|
|
61
70
|
/* left status marker (soft + outline; solid is already a full fill) */
|
|
62
71
|
.ui-toast--soft::before,
|
|
@@ -71,9 +80,16 @@
|
|
|
71
80
|
.ui-toast__title { font-weight: var(--weight-medium); color: var(--strong); }
|
|
72
81
|
.ui-toast__text { color: var(--dim); }
|
|
73
82
|
|
|
74
|
-
/* trailing action ("Undo" / "Retry")
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
/* trailing action ("Undo" / "Retry") — status-coloured TEXT, so it takes the
|
|
84
|
+
text-grade ink rather than the accent.
|
|
85
|
+
The hover wash is the page background rather than the accent again: a second
|
|
86
|
+
accent tint on an already accent-tinted ground moved the ground TOWARDS the
|
|
87
|
+
ink and took every soft pair below the floor. --bg is white in the light theme
|
|
88
|
+
and near-black in the dark one, so one expression lightens the ground where
|
|
89
|
+
the ink is dark and darkens it where the ink is bright — the hovered pair is
|
|
90
|
+
at or above the resting pair in both. */
|
|
91
|
+
.ui-toast__action { flex: none; align-self: center; background: none; border: 0; padding: 4px 9px; border-radius: var(--radius-xs); font: inherit; font-weight: var(--weight-medium); color: var(--toast-action-ink); cursor: pointer; }
|
|
92
|
+
.ui-toast__action:hover { background: color-mix(in srgb, var(--bg) 55%, transparent); }
|
|
77
93
|
|
|
78
94
|
.ui-toast__close { flex: none; align-self: flex-start; background: none; border: 0; color: var(--muted); cursor: pointer; padding: 2px; display: grid; place-items: center; }
|
|
79
95
|
.ui-toast__close svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 1.8; }
|
|
@@ -108,7 +124,10 @@
|
|
|
108
124
|
.ui-toast--solid .ui-toast__text { color: color-mix(in srgb, var(--toast-ink) 90%, transparent); }
|
|
109
125
|
.ui-toast--solid .ui-toast__icon { background: color-mix(in srgb, var(--toast-ink) 22%, transparent); color: var(--toast-ink); }
|
|
110
126
|
.ui-toast--solid .ui-toast__action { color: var(--toast-ink); }
|
|
111
|
-
|
|
127
|
+
/* Hover darkens the FILL rather than lightening the ink. Washing the ink over
|
|
128
|
+
itself was the one surface #149 did not reach, and it worked against the pair
|
|
129
|
+
it was drawn from: it pulled the fill towards the ink it has to clear. */
|
|
130
|
+
.ui-toast--solid .ui-toast__action:hover { background: color-mix(in srgb, var(--signal-contrast) 14%, transparent); }
|
|
112
131
|
.ui-toast--solid .ui-toast__close { color: color-mix(in srgb, var(--toast-ink) 68%, transparent); }
|
|
113
132
|
.ui-toast--solid .ui-toast__close:hover { color: var(--toast-ink); }
|
|
114
133
|
.ui-toast--solid .ui-toast__timer { background: color-mix(in srgb, var(--toast-ink) 55%, transparent); }
|