@apliteni/apliteni-ui 0.75.2 → 0.76.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/README.md +113 -240
- package/guidelines/accessibility-floor.md +2 -2
- package/guidelines/density-and-accents.md +14 -0
- package/package.json +1 -1
- package/react/dist/index.d.ts +2 -1
- package/react/dist/index.js +2 -2
- package/src/styles/base.css +5 -0
- package/src/styles/input.css +4 -0
- package/src/styles/table.css +35 -7
- package/src/tokens/tokens.css +7 -1
package/README.md
CHANGED
|
@@ -4,288 +4,161 @@
|
|
|
4
4
|
[](./LICENSE)
|
|
5
5
|
[](https://ui.apli.tech)
|
|
6
6
|
|
|
7
|
-
The Apliteni design system and UI kit
|
|
8
|
-
the text portal, `/account
|
|
7
|
+
The Apliteni design system and UI kit provides shared design tokens, components, themes and
|
|
8
|
+
UI rules for the strategy deck, the text portal, `/account` and future product surfaces.
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
One package carries two layers. **HTML + CSS** includes token CSS, component CSS and
|
|
11
|
+
HTML-string factories. Use this layer for server-rendered pages that do not use a component
|
|
12
|
+
framework. **React components** support surfaces with client-side state, such as tables,
|
|
13
|
+
filters, forms and modals. Both layers use the same `.ui-*` classes and tokens, so a page
|
|
14
|
+
can use both. Choose React when the surface needs state.
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
- **Package** → [`@apliteni/apliteni-ui`](https://www.npmjs.com/package/@apliteni/apliteni-ui) (public npm)
|
|
16
|
-
- **React components** → `@apliteni/apliteni-ui/react` — a subpath of the same package, source in [`react/`](./react)
|
|
17
|
-
|
|
18
|
-
## HTML + CSS *and* React
|
|
19
|
-
|
|
20
|
-
The strategy portal (`apliteni/strategy`, `viz/`) server-renders HTML strings from
|
|
21
|
-
`.mjs` modules without a component framework. The kit supplies token CSS, component
|
|
22
|
-
CSS, and HTML-string factories that the portal can import without a rewrite.
|
|
23
|
-
Storybook (`@storybook/html-vite`) renders those same strings.
|
|
24
|
-
|
|
25
|
-
React components handle client state in dashboards, tables, filters, and forms.
|
|
26
|
-
They use the vanilla kit's `.ui-*` classes and tokens.
|
|
27
|
-
|
|
28
|
-
**Which one:** does the surface hold meaningful client state? No → the HTML-string
|
|
29
|
-
factories below. Yes → the [React components](#react-components-stateful-surfaces).
|
|
30
|
-
|
|
31
|
-
Either layer follows the same UI rules — what one page may hold, which component to reach
|
|
32
|
-
for, the states it owes, how colour and wording work. They live in the **Guidelines** section
|
|
33
|
-
of Storybook, which opens on
|
|
34
|
-
[links to every guideline page](https://ui.apli.tech/storybook/?path=/story/guidelines-overview--overview).
|
|
35
|
-
The same guidance ships as plain Markdown in
|
|
36
|
-
`node_modules/@apliteni/apliteni-ui/guidelines/`. Start with
|
|
37
|
-
[guidelines/overview.md](guidelines/overview.md); Storybook reads these documents too.
|
|
38
|
-
|
|
39
|
-
[The page](https://ui.apli.tech/storybook/?path=/story/guidelines-the-page--the-page) is the one
|
|
40
|
-
to read before you design a screen: the limits one page keeps, whatever it is about.
|
|
16
|
+
Every component is in Storybook at **[ui.apli.tech](https://ui.apli.tech)**.
|
|
41
17
|
|
|
42
18
|
## Install
|
|
43
19
|
|
|
44
|
-
|
|
20
|
+
The package is published on the public npm registry. You do not need scope configuration or
|
|
21
|
+
an npm token:
|
|
45
22
|
|
|
46
23
|
```bash
|
|
47
|
-
npm install @apliteni/apliteni-ui
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## Use it
|
|
51
|
-
|
|
52
|
-
```js
|
|
53
|
-
import '@apliteni/apliteni-ui/css'; // once, at app root (load the two fonts too — see below)
|
|
54
|
-
import { button, card, topbar, wireTopbar } from '@apliteni/apliteni-ui';
|
|
55
|
-
|
|
56
|
-
el.innerHTML = topbar({ word: 'Strategy', account: { name, email } })
|
|
57
|
-
+ card({ title: 'Appearance', body: button({ label: 'Save', variant: 'primary' }) });
|
|
58
|
-
wireTopbar(document); // theme toggle, menus, segmented, copy buttons
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### The two fonts
|
|
62
|
-
|
|
63
|
-
The kit names two families and bundles neither, so the host page loads them. Poppins is
|
|
64
|
-
`--font-display` — headings, brand marks, large readouts. IBM Plex Sans is `--font-sans` —
|
|
65
|
-
tables, fields, paragraphs, chat, which is most of an application. Weights 300-700 in both:
|
|
66
|
-
|
|
67
|
-
```html
|
|
68
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
69
|
-
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&family=IBM+Plex+Sans:wght@300;400;500;600;700&display=swap">
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
If either font is missing, that role falls back to its system stack, and nothing says so.
|
|
73
|
-
For the old single-family appearance, set both roles to the same family in your stylesheet,
|
|
74
|
-
after the kit's:
|
|
75
|
-
|
|
76
|
-
```css
|
|
77
|
-
:root { --font-sans: var(--font-display); }
|
|
24
|
+
npm install @apliteni/apliteni-ui react react-dom
|
|
78
25
|
```
|
|
79
26
|
|
|
80
|
-
|
|
27
|
+
Install React and React DOM yourself, using version 18 or newer. The kit does not declare
|
|
28
|
+
either package as a regular or peer dependency. npm therefore does not install them or warn
|
|
29
|
+
when they are missing. If they are missing, importing `@apliteni/apliteni-ui/react` fails
|
|
30
|
+
at build time or runtime with a module-not-found error. A plain HTML consumer installs only
|
|
31
|
+
the kit and is unaffected because React is not added to its dependency tree.
|
|
81
32
|
|
|
82
|
-
The
|
|
83
|
-
|
|
33
|
+
The React components are a subpath of this package, not a separate package. You get one
|
|
34
|
+
install, one version and one version pin.
|
|
84
35
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
sub: 'How the portal looks and speaks to you.',
|
|
94
|
-
body: card({ title: 'Appearance', body: switchToggle({ label: 'Reduce motion' }) }),
|
|
95
|
-
});
|
|
96
|
-
wireTopbar(el); // menus, theme toggle, segmented controls
|
|
97
|
-
wireShell(el); // the toggle that folds the rail, the reader's menu, the nav's groups
|
|
98
|
-
|
|
99
|
-
// Custom sidebar nav? pass `nav: [['prefs','gear','Preferences'], ['billing','wallet','Billing']]`
|
|
100
|
-
// A page that will never call wireShell()? pass `collapsible: false` and no toggle is drawn
|
|
101
|
-
```
|
|
36
|
+
| Import | What it is |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `@apliteni/apliteni-ui/css` | Tokens and every component stylesheet. Import once at the app root. |
|
|
39
|
+
| `@apliteni/apliteni-ui` | The HTML-string factories: `button()`, `card()`, `topbar()`, `accountShell()` and the rest. |
|
|
40
|
+
| `@apliteni/apliteni-ui/react` | The React components, TypeScript types included. |
|
|
41
|
+
| `@apliteni/apliteni-ui/react/css` | Their shell styles. |
|
|
42
|
+
| `@apliteni/apliteni-ui/inline` | The same CSS as strings, for a server that inlines it. |
|
|
43
|
+
| `@apliteni/apliteni-ui/guidelines/*` | The UI rules as Markdown. |
|
|
102
44
|
|
|
103
|
-
|
|
104
|
-
as **strings** instead:
|
|
45
|
+
## Use it
|
|
105
46
|
|
|
106
47
|
```js
|
|
107
|
-
import
|
|
108
|
-
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
`cssText` includes every sheet. When selecting individual sheets from `styles`,
|
|
112
|
-
`successPanel()` needs both `styles.callout` (panel layout) and `styles.success`
|
|
113
|
-
(shared glowing check and reduced-motion styles), after `tokensCss` and `baseCss`.
|
|
114
|
-
|
|
115
|
-
## React components (stateful surfaces)
|
|
116
|
-
|
|
117
|
-
`DataTable`, `Pagination`, `Modal`, `Button`, `Badge`, `Card` and `Icon` — same `.ui-*` classes,
|
|
118
|
-
same tokens, TypeScript types included. They ship as a **subpath of this package**,
|
|
119
|
-
not as a package of their own: one install, one version, one pin.
|
|
48
|
+
import '@apliteni/apliteni-ui/css';
|
|
49
|
+
import { topbar, card, button, wireTopbar } from '@apliteni/apliteni-ui';
|
|
120
50
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
```bash
|
|
126
|
-
npm install @apliteni/apliteni-ui react react-dom
|
|
51
|
+
el.innerHTML = topbar({ word: 'Strategy', account: { name, email } })
|
|
52
|
+
+ card({ title: 'Appearance', body: button({ label: 'Save', variant: 'primary' }) });
|
|
53
|
+
wireTopbar(document); // theme toggle, menus, segmented controls, copy buttons
|
|
127
54
|
```
|
|
128
55
|
|
|
129
|
-
Without them, importing `@apliteni/apliteni-ui/react` fails at build or runtime with
|
|
130
|
-
a module-not-found error for `react`. Plain HTML consumers can install the kit alone;
|
|
131
|
-
the other entry points are unaffected and do not bring React into the dependency tree.
|
|
132
|
-
|
|
133
56
|
```tsx
|
|
134
|
-
import '@apliteni/apliteni-ui/css';
|
|
135
|
-
import '@apliteni/apliteni-ui/react/css';
|
|
57
|
+
import '@apliteni/apliteni-ui/css';
|
|
58
|
+
import '@apliteni/apliteni-ui/react/css';
|
|
136
59
|
import { DataTable, Modal } from '@apliteni/apliteni-ui/react';
|
|
137
60
|
```
|
|
138
61
|
|
|
139
|
-
The
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
6007 can drift into and composes the first port that proves it is this workspace's
|
|
143
|
-
Storybook. A stranger on the port is never composed — the "React components"
|
|
144
|
-
section is absent instead, and the terminal says why. Details in
|
|
145
|
-
[`react/README.md`](./react/README.md).
|
|
62
|
+
The `accountShell()` factory provides the complete `/account` layout: the topbar, sticky
|
|
63
|
+
sidebar and page body, so no product rebuilds it. The
|
|
64
|
+
[component catalog](docs/library.md#component-catalog) lists this factory and every other one.
|
|
146
65
|
|
|
147
|
-
###
|
|
66
|
+
### Fonts
|
|
148
67
|
|
|
149
|
-
|
|
68
|
+
The kit defines two font families but does not bundle them, so the host page must load them.
|
|
69
|
+
Poppins is `--font-display`, used for headings, brand marks and large readouts. IBM Plex Sans
|
|
70
|
+
is `--font-sans`, used for tables, fields, paragraphs, chat and most application text. Both
|
|
71
|
+
families support weights 300–700.
|
|
150
72
|
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const control = host.querySelector('button');
|
|
155
|
-
setButtonBusy(control, { busy: true });
|
|
156
|
-
// When the request completes:
|
|
157
|
-
setButtonBusy(control, { busy: false });
|
|
73
|
+
```html
|
|
74
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
75
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&family=IBM+Plex+Sans:wght@300;400;500;600;700&display=swap">
|
|
158
76
|
```
|
|
159
77
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
Explicit `disabled: true` stays natively disabled. Static `button({ busy: true })`
|
|
163
|
-
markup uses native disabled until the helper wires it; call `setButtonBusy` to
|
|
164
|
-
switch to the focus-preserving behavior. React `<Button busy={saving}>Save changes</Button>` uses the same treatment.
|
|
165
|
-
The action label slides down, three dots take its place, and the label returns from below
|
|
166
|
-
when complete. Its hidden label preserves the width and accessible name. React retains
|
|
167
|
-
the last ready children while busy; changed children appear on completion. Reduced
|
|
168
|
-
motion switches immediately to static dots.
|
|
169
|
-
See Storybook's **Button / Busy Transition** for the live vanilla example.
|
|
170
|
-
|
|
171
|
-
## Theming
|
|
172
|
-
|
|
173
|
-
React forms can use [Checkbox, radio and Switch](react/README.md#checkbox-and-switch)
|
|
174
|
-
with native labels, keyboard behavior and controlled or uncontrolled state.
|
|
78
|
+
If a family does not load, the browser uses its system fallback stack. The kit does not
|
|
79
|
+
display a message about this fallback.
|
|
175
80
|
|
|
176
|
-
|
|
177
|
-
to save dark, light or auto choices. Auto resolves to dark or light on the root.
|
|
81
|
+
## Theme and accent
|
|
178
82
|
|
|
179
|
-
|
|
180
|
-
`data-accent
|
|
83
|
+
Set the theme on `<html>` with `data-theme="dark|light"`. You can set an accent separately
|
|
84
|
+
with `data-accent`:
|
|
181
85
|
|
|
182
86
|
```html
|
|
183
87
|
<html data-theme="dark" data-accent="phoenix">
|
|
184
88
|
```
|
|
185
89
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
component
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
**
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
`SidebarNav` renders flat items or captioned sections with one level of nested
|
|
227
|
-
disclosure, counts, disabled and danger rows, an artwork slot, and a footer slot. Its
|
|
228
|
-
`renderLink(item, linkProps)` hook supports router links; spread the supplied props
|
|
229
|
-
to retain names, children and current-page semantics. `collapsed` changes only
|
|
230
|
-
presentation, keeping nested links reachable and named.
|
|
90
|
+
An accent changes only the accent family: `--accent`, `--purple*`, `--glow-purple`, `--ring`
|
|
91
|
+
and `--grad-*`. Surfaces, text and signal colours remain unchanged. Green remains the live
|
|
92
|
+
colour and pink remains the danger colour. As a result, every accent works in both themes
|
|
93
|
+
without requiring component changes. The shipped accents are **Nebula** (purple and the
|
|
94
|
+
default), **Phoenix** (ember), **Ocean** (azure) and **Emerald** (jade).
|
|
95
|
+
|
|
96
|
+
Both attributes override the defaults. If neither is present, the kit uses dark Nebula. If
|
|
97
|
+
only `data-accent` is present, the kit uses that accent with the dark theme. An absent
|
|
98
|
+
`data-theme` does *not* mean “follow the system”: the kit has no `prefers-color-scheme`
|
|
99
|
+
rule. If the host wants to follow the operating system preference, its JavaScript must
|
|
100
|
+
choose the theme and add the attribute. See
|
|
101
|
+
[an absent attribute means dark](docs/library.md#an-absent-attribute-means-dark).
|
|
102
|
+
|
|
103
|
+
You can change the settings at runtime with `applyTheme('light')` and
|
|
104
|
+
`applyAccent('phoenix')`. These functions save the choices to `localStorage`. You can also
|
|
105
|
+
use `accentPicker()`, which `wireTopbar()` connects.
|
|
106
|
+
|
|
107
|
+
## The rules
|
|
108
|
+
|
|
109
|
+
The **Guidelines** explain what a page may contain, which component to choose, which states
|
|
110
|
+
it must support, and how to handle colour and wording. Both kit layers follow these rules.
|
|
111
|
+
The guidelines ship as Markdown in the package and are also available as a Storybook
|
|
112
|
+
section.
|
|
113
|
+
|
|
114
|
+
- [guidelines/overview.md](guidelines/overview.md) — the index, and the rules the kit has yet to meet.
|
|
115
|
+
- [guidelines/the-page.md](guidelines/the-page.md) — read this one before designing a screen.
|
|
116
|
+
- [Guidelines in Storybook](https://ui.apli.tech/storybook/?path=/story/guidelines-overview--overview) — the same pages with live specimens.
|
|
117
|
+
|
|
118
|
+
## Documentation
|
|
119
|
+
|
|
120
|
+
| Page | What it answers |
|
|
121
|
+
| --- | --- |
|
|
122
|
+
| [specification.md](docs/specification.md) | What the kit guarantees and what it refuses to do. A gate on `npm test` holds every statement. |
|
|
123
|
+
| [library.md](docs/library.md) | Architecture, the `src/` layout, tokens and theming, the component catalog. |
|
|
124
|
+
| [react/README.md](react/README.md) | The React components, their props, and the Storybook on port 6007. |
|
|
125
|
+
| [storybook.md](docs/storybook.md) | The workbench: config, theming toolbar, story conventions. |
|
|
126
|
+
| [landing-page.md](docs/landing-page.md) | ui.apli.tech: the chrome, the static build, the hosting. |
|
|
127
|
+
| [changelog.md](docs/changelog.md) | The changelog's data model and its Storybook deeplinks. |
|
|
231
128
|
|
|
232
129
|
## Contribute
|
|
233
130
|
|
|
234
|
-
Use Node 20 or newer. Install [jq](https://jqlang.github.io/jq/), then run
|
|
235
|
-
the repository root. Start Storybook with `npm run storybook`. For React, use `npm run
|
|
236
|
-
storybook -w react`. Before opening a PR, run `npm test`, `npm test -w react`, `npm run
|
|
237
|
-
build-storybook`, and `node site/build.mjs`.
|
|
238
|
-
|
|
239
|
-
Add a general, composable kit component only when all three checks are true:
|
|
240
|
-
(1) the issue names at least two products that use it or have requested it;
|
|
241
|
-
(2) existing components cannot provide it without copying their markup or logic;
|
|
242
|
-
(3) it contains no domain-specific data or rules. Otherwise, build the UI by
|
|
243
|
-
composition and add a Storybook showcase. Keep the vanilla kit’s existing look.
|
|
244
|
-
|
|
245
|
-
To add a component, put its token-based CSS in `src/styles/`. Include it in
|
|
246
|
-
`src/index.css` and in both the `styles` map and `cssText` in `src/inline.js`. Add the
|
|
247
|
-
HTML factory under `src/components/`, export it from `src/index.js`, and add a
|
|
248
|
-
playground and state examples under `stories/components/`. Put React components in
|
|
249
|
-
`react/src/`, with a test and story that use the shared classes and tokens. Read the
|
|
250
|
-
[guidelines](guidelines/overview.md) for the design rules.
|
|
251
|
-
|
|
252
|
-
Open an issue, branch from `main`, and link the issue in your PR. Changes to published
|
|
253
|
-
files, including this README, require a version bump in `package.json` and
|
|
254
|
-
`package-lock.json`, plus an entry in `site/changelog.mjs`. Merging the bump starts the
|
|
255
|
-
release workflow. Do not push a tag or publish by hand.
|
|
256
|
-
|
|
257
|
-
[Agent rules](AGENTS.md) cover review, data handling, and release checks.
|
|
131
|
+
Use Node 20 or newer. Install [jq](https://jqlang.github.io/jq/), then run:
|
|
258
132
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
service builds from `main` with:
|
|
264
|
-
|
|
265
|
-
```
|
|
266
|
-
npm ci && npm run build-storybook && node site/build.mjs
|
|
133
|
+
```bash
|
|
134
|
+
npm ci
|
|
135
|
+
npm run storybook # the kit; add -w react for the React workspace
|
|
136
|
+
npm test && npm test -w react && npm run build-storybook && node site/build.mjs
|
|
267
137
|
```
|
|
268
138
|
|
|
269
|
-
|
|
270
|
-
and the Storybook folded in at `/storybook`. Push to `main` and redeploy the `site`
|
|
271
|
-
service to roll it out.
|
|
139
|
+
Run the final command before opening a PR.
|
|
272
140
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
```
|
|
141
|
+
Add a general, composable kit component only when all three conditions are true: the issue
|
|
142
|
+
names at least two products that use it or have requested it; existing components cannot
|
|
143
|
+
provide it without copying markup or logic; and it contains no domain-specific data or
|
|
144
|
+
rules. If any condition is missing, compose the UI from existing components and add a
|
|
145
|
+
Storybook showcase. Preserve the vanilla kit's existing look.
|
|
279
146
|
|
|
280
|
-
|
|
147
|
+
To add a component, put its token-based CSS in `src/styles/`. Include that CSS in
|
|
148
|
+
`src/index.css` and in both the `styles` map and `cssText` in `src/inline.js`. Put the HTML
|
|
149
|
+
factory in `src/components/` and export it from `src/index.js`. Add a playground and state
|
|
150
|
+
examples in `stories/components/`. Put React components in `react/src/`; each one needs a
|
|
151
|
+
test and a story that use the shared classes and tokens. The
|
|
152
|
+
[guidelines](guidelines/overview.md) define the design rules.
|
|
281
153
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
154
|
+
Open an issue, create a branch from `main`, and link the issue in your PR. If you change a
|
|
155
|
+
published file, including this README, bump the version in `package.json` and
|
|
156
|
+
`package-lock.json`, and add an entry to `site/changelog.mjs`. Merging that version bump
|
|
157
|
+
starts the release workflow. Do not push a tag or publish manually.
|
|
158
|
+
[AGENTS.md](AGENTS.md) describes the review, data-handling and release checks.
|
|
287
159
|
|
|
288
160
|
## License
|
|
289
161
|
|
|
290
|
-
[MIT](./LICENSE) © Apliteni
|
|
291
|
-
|
|
162
|
+
[MIT](./LICENSE) © Apliteni, for the **code**. The Apliteni name, logos and brand marks
|
|
163
|
+
are trademarks and are **not** covered by the MIT license — see
|
|
164
|
+
[TRADEMARK.md](./TRADEMARK.md).
|
|
@@ -56,13 +56,13 @@
|
|
|
56
56
|
|
|
57
57
|
<!-- rule: disabled-legibility -->
|
|
58
58
|
|
|
59
|
-
**Rule:** Give a disabled control its own ink and
|
|
59
|
+
**Rule:** Give a disabled control its own ink, surface and edge, and take the accent off it.
|
|
60
60
|
|
|
61
61
|
**Do:** Unavailable reads as unavailable with the pointer nowhere near it.
|
|
62
62
|
|
|
63
63
|
**Don't:** Unavailable keeps the live paint, so only the cursor reports the state.
|
|
64
64
|
|
|
65
|
-
**Except:** A disabled label must stay legible, so the pair changes rather than fades. Only the label-free switch track may fade.
|
|
65
|
+
**Except:** A disabled label must stay legible, so the pair changes rather than fades. Only the label-free switch track may fade, and a control that draws no box when it is on — a ghost button — draws none when it is off, so its ink carries the state alone. Where the browser fades a control for you, as Chromium does a disabled select, reset that fade: it takes the edge and the words down together, and the words are what a reader still has to read.
|
|
66
66
|
|
|
67
67
|
## Touch field text
|
|
68
68
|
|
|
@@ -37,3 +37,17 @@
|
|
|
37
37
|
**Don't:** Make both actions primary or colour every label and value with the accent.
|
|
38
38
|
|
|
39
39
|
**Except:** Keep [Badge success, pending and danger](https://ui.apli.tech/storybook/?path=/story/components-badge-status--badges) for their named statuses; do not replace those signals with the accent.
|
|
40
|
+
|
|
41
|
+
## Follow the consequence
|
|
42
|
+
|
|
43
|
+
<!-- rule: follow-the-consequence -->
|
|
44
|
+
|
|
45
|
+
**Rule:** Where a screen shows a source beside the values that will be saved, give the saved values the leading position, the wider column, the heavier weight and whatever accent the pair carries; leave the source with none of the four.
|
|
46
|
+
|
|
47
|
+
**Why:** Emphasis tells the reader what the screen is for. A preview that outweighs the data being committed makes a commitment look like a reading task, and an accent on the reference says the reference is the point. The accent goes on the name because a line drawn strongly enough to carry the signal is an outlined box, which the kit does not draw.
|
|
48
|
+
|
|
49
|
+
**Do:** Lead with the extracted fields, in the wider column, with each value heavier than its label, and put the accent on the pane's own name.
|
|
50
|
+
|
|
51
|
+
**Don't:** Give the source document the leading, wider pane, or let a control inside it hold the only accent on the screen while the saved values hold none.
|
|
52
|
+
|
|
53
|
+
**Except:** Fade nothing to make a block quieter; a quieter block is smaller, later or uncoloured, never muted. See [Limit muted ink](https://ui.apli.tech/storybook/?path=/story/guidelines-labels-and-titles--page).
|
package/package.json
CHANGED
package/react/dist/index.d.ts
CHANGED
|
@@ -72,10 +72,11 @@ declare const StatusDot: react.ForwardRefExoticComponent<Omit<HTMLAttributes<HTM
|
|
|
72
72
|
} & react.RefAttributes<HTMLSpanElement>>;
|
|
73
73
|
|
|
74
74
|
type Level = 2 | 3 | 4 | 5 | 6;
|
|
75
|
-
declare function Card({ title, sub, level, children }: {
|
|
75
|
+
declare function Card({ title, sub, level, className, children }: {
|
|
76
76
|
title?: ReactNode;
|
|
77
77
|
sub?: ReactNode;
|
|
78
78
|
level?: Level;
|
|
79
|
+
className?: string;
|
|
79
80
|
children?: ReactNode;
|
|
80
81
|
}): react.JSX.Element;
|
|
81
82
|
|
package/react/dist/index.js
CHANGED
|
@@ -244,11 +244,11 @@ var StatusDot = forwardRef5(function StatusDot2({
|
|
|
244
244
|
|
|
245
245
|
// src/primitives/Card.tsx
|
|
246
246
|
import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
247
|
-
function Card({ title, sub, level = 2, children }) {
|
|
247
|
+
function Card({ title, sub, level = 2, className, children }) {
|
|
248
248
|
const n = Number(level);
|
|
249
249
|
const Heading = `h${[2, 3, 4, 5, 6].includes(n) ? n : 2}`;
|
|
250
250
|
const titled = title != null && title !== false && title !== "";
|
|
251
|
-
return /* @__PURE__ */ jsxs4("div", { className: "ui-card", children: [
|
|
251
|
+
return /* @__PURE__ */ jsxs4("div", { className: ["ui-card", className].filter(Boolean).join(" "), children: [
|
|
252
252
|
titled && /* @__PURE__ */ jsx8(Heading, { className: "ui-card__title", children: title }),
|
|
253
253
|
sub != null && /* @__PURE__ */ jsx8("div", { className: "ui-card__sub", children: sub }),
|
|
254
254
|
children
|
package/src/styles/base.css
CHANGED
|
@@ -137,11 +137,16 @@ svg:where(:not([width]):not([height])) {
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
/* Focus-visible ring, shared by all interactive controls */
|
|
140
|
+
/* `.toggle` is the theme switch — a real button that wears neither .ui-btn nor
|
|
141
|
+
.ui-focusable, in the vanilla topbar, React ThemeToggle and the React shell's bar
|
|
142
|
+
alike, so keyboard focus on it fell back to the browser's own outline in all three.
|
|
143
|
+
It has no class of its own to opt in with; it joins the list instead. Found on #385. */
|
|
140
144
|
.ui-focusable:focus-visible,
|
|
141
145
|
.ui-snippet :focus-visible,
|
|
142
146
|
.ui-btn:focus-visible,
|
|
143
147
|
.ui-input:focus-visible,
|
|
144
148
|
.ui-seg button:focus-visible,
|
|
149
|
+
.toggle:focus-visible,
|
|
145
150
|
.ui-switch:focus-visible {
|
|
146
151
|
outline: 2px solid transparent;
|
|
147
152
|
box-shadow: var(--ring);
|
package/src/styles/input.css
CHANGED
|
@@ -55,6 +55,9 @@
|
|
|
55
55
|
UA's grey-out, so a disabled one had looked exactly like a live one.
|
|
56
56
|
`background-color`, not `background` — the shorthand also resets the three
|
|
57
57
|
longhands the chevron is drawn with (#511). Held by stories/select-chevron.test.js.
|
|
58
|
+
`opacity` is the other half of that grey-out, and no colour here outranks it, so
|
|
59
|
+
the fade is reset below — it took the edge and the words down together. #564,
|
|
60
|
+
argued at docs/specification.md#colour-and-contrast.
|
|
58
61
|
why: docs/specification.md#icons-and-glyphs */
|
|
59
62
|
.ui-input:disabled,
|
|
60
63
|
.ui-select:disabled,
|
|
@@ -62,6 +65,7 @@
|
|
|
62
65
|
background-color: var(--disabled-surface); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */
|
|
63
66
|
border-color: var(--disabled-border);
|
|
64
67
|
/* muted-ink: state — unavailable control */ color: var(--disabled-ink);
|
|
68
|
+
opacity: 1; /* fade-reset: the UA's select:disabled grey-out. #564 */
|
|
65
69
|
cursor: not-allowed;
|
|
66
70
|
}
|
|
67
71
|
|
package/src/styles/table.css
CHANGED
|
@@ -17,14 +17,32 @@
|
|
|
17
17
|
padding: 0 var(--space-4) var(--space-3) 0;
|
|
18
18
|
border-bottom: 1px solid var(--border);
|
|
19
19
|
}
|
|
20
|
-
|
|
20
|
+
/* A row header is a body cell that happens to name its row, so it takes the body cell's
|
|
21
|
+
* padding. `.ui-table th` pads for the head, where the label sits above the rule and
|
|
22
|
+
* wants no space over it; inside the body or the footer that left the name sitting low
|
|
23
|
+
* against the values beside it. `:is()` here is only for specificity — two type
|
|
24
|
+
* selectors outrank `.ui-table th`, which states the head's padding after this. */
|
|
25
|
+
.ui-table td,
|
|
26
|
+
.ui-table :is(tbody, tfoot) th {
|
|
21
27
|
padding: var(--space-4) var(--space-4) var(--space-4) 0;
|
|
22
28
|
border-bottom: 1px solid var(--border);
|
|
23
29
|
color: var(--text);
|
|
24
30
|
vertical-align: middle;
|
|
25
31
|
white-space: nowrap;
|
|
26
32
|
}
|
|
27
|
-
|
|
33
|
+
/* The last row of the last section, so a final body row keeps its separator when a
|
|
34
|
+
* footer follows it. `tr:last-child td` closed the wrong edge: it stripped the border
|
|
35
|
+
* from the last row of every section, leaving items and totals with nothing between
|
|
36
|
+
* them. why: docs/specification.md#spacing-and-rhythm */
|
|
37
|
+
.ui-table > :last-child > tr:last-child > :is(th, td) { border-bottom: 0; }
|
|
38
|
+
/* Totals open with the 2px rule the zebra header already uses, so a reader can see
|
|
39
|
+
* where items end and totals begin; five identical hairlines told them nothing.
|
|
40
|
+
* Collapsed borders paint one edge, so this meets the last body row's hairline and the
|
|
41
|
+
* wider of the two wins — no doubled line.
|
|
42
|
+
* A footer label is a row header for the figure beside it, so it sits against that
|
|
43
|
+
* figure rather than against the first column it spans. */
|
|
44
|
+
.ui-table tfoot tr:first-child > :is(th, td) { border-top: 2px solid var(--border-strong); }
|
|
45
|
+
.ui-table tfoot th { text-align: right; }
|
|
28
46
|
.ui-table td.ui-table__title { color: var(--strong); font-weight: var(--weight-medium); width: 99%; white-space: normal; }
|
|
29
47
|
.ui-table th:last-child { padding-right: 0; }
|
|
30
48
|
.ui-table td.ui-table__act { padding-right: 0; text-align: right; }
|
|
@@ -62,14 +80,16 @@
|
|
|
62
80
|
* why: docs/specification.md#spacing-and-rhythm
|
|
63
81
|
* held by: stories/table-rhythm.test.js */
|
|
64
82
|
.ui-table--dense th { padding: 0 var(--space-3) var(--space-2); }
|
|
65
|
-
.ui-table--dense td
|
|
83
|
+
.ui-table--dense td,
|
|
84
|
+
.ui-table--dense :is(tbody, tfoot) th { padding: var(--space-2) var(--space-3); font-size: var(--text-sm); white-space: nowrap; }
|
|
66
85
|
.ui-table--dense th:first-child,
|
|
67
86
|
.ui-table--dense td:first-child { padding-left: var(--space-3); }
|
|
68
87
|
.ui-table--dense th:last-child,
|
|
69
88
|
.ui-table--dense td:last-child { padding-right: var(--space-3); }
|
|
70
89
|
|
|
71
|
-
.ui-table--zebra th { border-bottom-width: 2px; }
|
|
72
|
-
.ui-table--zebra td
|
|
90
|
+
.ui-table--zebra thead th { border-bottom-width: 2px; }
|
|
91
|
+
.ui-table--zebra td,
|
|
92
|
+
.ui-table--zebra :is(tbody, tfoot) th { border-bottom: 0; }
|
|
73
93
|
.ui-table--zebra tbody tr:nth-child(even) td { background: var(--surface-2); --ring-gap: var(--surface-2); }
|
|
74
94
|
/* The stripe stays full-bleed (the #71 inset above is hover only), but it only reads right
|
|
75
95
|
* if the end cells are inset from the row's own edge, and `.ui-table td` sets
|
|
@@ -116,8 +136,15 @@
|
|
|
116
136
|
}
|
|
117
137
|
|
|
118
138
|
.ui-table__num { text-align: right; font-variant-numeric: tabular-nums; }
|
|
119
|
-
|
|
139
|
+
/* A numeric column is sized to its content while .ui-table__title claims the rest,
|
|
140
|
+
* so a two-word numeric header wraps where none of its values ever do. */
|
|
141
|
+
.ui-table th.ui-table__num { text-align: right; white-space: nowrap; }
|
|
120
142
|
.ui-table__num--strong { color: var(--strong); font-weight: var(--weight-semibold); }
|
|
143
|
+
/* `.ui-table th` and `.ui-table td` each state their own ink, and `th` its weight too,
|
|
144
|
+
* so both outranked the helper: a totals row marked strong came out in body ink, and
|
|
145
|
+
* bold only in the figure. Scoped to the table, the helper wins on either cell and the
|
|
146
|
+
* row reads as one row. */
|
|
147
|
+
.ui-table :is(th, td).ui-table__num--strong { color: var(--strong); font-weight: var(--weight-semibold); }
|
|
121
148
|
.ui-table__code { font-family: var(--font-mono); font-size: var(--text-sm); color: var(--text); }
|
|
122
149
|
|
|
123
150
|
/* The empty state (.ui-empty) — shown when a table/list has no rows — now
|
|
@@ -155,7 +182,8 @@
|
|
|
155
182
|
padding-inline: 0;
|
|
156
183
|
}
|
|
157
184
|
.ui-table td.ui-table__num { text-align: right; }
|
|
158
|
-
.ui-table--compact td
|
|
185
|
+
.ui-table--compact td,
|
|
186
|
+
.ui-table--compact :is(tbody, tfoot) th { padding: var(--space-1) var(--space-3); font-size: var(--text-sm); line-height: var(--leading-snug); height: 33px; }
|
|
159
187
|
.ui-table--compact th { padding: var(--space-2) var(--space-3); }
|
|
160
188
|
.ui-table--compact th:last-child,
|
|
161
189
|
.ui-table--compact td:last-child { padding-right: var(--space-3); }
|
package/src/tokens/tokens.css
CHANGED
|
@@ -194,7 +194,11 @@
|
|
|
194
194
|
why: docs/specification.md#colour-and-contrast */
|
|
195
195
|
--disabled-ink: var(--muted);
|
|
196
196
|
--disabled-surface: var(--surface);
|
|
197
|
-
|
|
197
|
+
/* One rung below the hairline, so an off box is not the live box with quieter words
|
|
198
|
+
in it: dark answered both with --border. 1.16:1 on the card against the live
|
|
199
|
+
1.27:1, and as far down as the headroom goes. #564
|
|
200
|
+
why: docs/specification.md#colour-and-contrast */
|
|
201
|
+
--disabled-border: var(--surface-3);
|
|
198
202
|
/* The ink of a disabled control that paints no box of its own — a ghost button. With
|
|
199
203
|
no surface beside it, it is read on whatever is behind it, so it is set to clear the
|
|
200
204
|
floor on the dullest ground there is (--surface-3). #273
|
|
@@ -371,6 +375,8 @@
|
|
|
371
375
|
/* Disabled text stays on the card surface; its ink marks the unavailable state. */
|
|
372
376
|
--disabled-ink: var(--muted);
|
|
373
377
|
--disabled-surface: var(--surface);
|
|
378
|
+
/* The hairline, where dark takes the rung under it: light's field edge is already
|
|
379
|
+
--border-strong, so the two states read 1.52:1 and 1.24:1. #564 */
|
|
374
380
|
--disabled-border: var(--border);
|
|
375
381
|
--disabled-ink-bare: #585e6c;
|
|
376
382
|
|