@apliteni/apliteni-ui 0.25.0 → 0.25.3
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/package.json +1 -1
- package/react/README.md +8 -0
- package/react/dist/index.js +24 -9
- package/src/styles/callout.css +1 -1
- package/src/styles/card.css +7 -0
- package/src/styles/drawer.css +1 -0
- package/src/styles/dropdown.css +13 -1
- package/src/styles/feedback.css +11 -1
- package/src/styles/topbar.css +13 -0
package/package.json
CHANGED
package/react/README.md
CHANGED
|
@@ -31,6 +31,14 @@ import { DataTable, Modal, Button } from '@apliteni/apliteni-ui/react';
|
|
|
31
31
|
|
|
32
32
|
Components: `DataTable`, `Modal`, `Button`, `Badge`, `Card`, `Icon`.
|
|
33
33
|
|
|
34
|
+
## What the Modal does with focus
|
|
35
|
+
|
|
36
|
+
Opening moves focus to the first eligible control in the body, in DOM order, or to
|
|
37
|
+
the dialog itself if none exists. Links and disclosure summaries are eligible; hidden
|
|
38
|
+
controls, disabled controls, controls inside a closed disclosure and elements with a
|
|
39
|
+
negative tabindex are skipped. Tab and Shift+Tab wrap at the ends of the same list.
|
|
40
|
+
Escape and a click on the scrim dismiss the dialog and return focus to its opener.
|
|
41
|
+
|
|
34
42
|
## Work on them
|
|
35
43
|
|
|
36
44
|
From the repo root — one `npm install` covers the workspace:
|
package/react/dist/index.js
CHANGED
|
@@ -84,7 +84,25 @@ function Card({ title, sub, children }) {
|
|
|
84
84
|
import { useEffect, useRef } from "react";
|
|
85
85
|
import { createPortal } from "react-dom";
|
|
86
86
|
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
87
|
-
var FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
87
|
+
var FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]), details > summary:first-of-type';
|
|
88
|
+
function tabbable(el) {
|
|
89
|
+
const tabindex = el.getAttribute("tabindex");
|
|
90
|
+
if (tabindex !== null && Number(tabindex) < 0) return false;
|
|
91
|
+
if (el.matches(":disabled")) return false;
|
|
92
|
+
for (let node = el; node; node = node.parentElement) {
|
|
93
|
+
if (node.inert || node.hasAttribute("inert") || node.hasAttribute("hidden")) return false;
|
|
94
|
+
const holder = node.parentElement;
|
|
95
|
+
const folded = holder?.tagName === "DETAILS" && !holder.open;
|
|
96
|
+
if (folded && node !== holder.querySelector(":scope > summary")) return false;
|
|
97
|
+
}
|
|
98
|
+
return typeof el.checkVisibility !== "function" || el.checkVisibility({ visibilityProperty: true });
|
|
99
|
+
}
|
|
100
|
+
var tabbablesIn = (root) => Array.from(root.querySelectorAll(FOCUSABLE)).filter(tabbable);
|
|
101
|
+
var dismissOnScrim = (onClose) => (e) => {
|
|
102
|
+
if (e.target !== e.currentTarget) return;
|
|
103
|
+
e.preventDefault();
|
|
104
|
+
onClose();
|
|
105
|
+
};
|
|
88
106
|
function Modal({ open, title, onClose, footer, children }) {
|
|
89
107
|
const panel = useRef(null);
|
|
90
108
|
useEffect(() => {
|
|
@@ -95,7 +113,7 @@ function Modal({ open, title, onClose, footer, children }) {
|
|
|
95
113
|
return;
|
|
96
114
|
}
|
|
97
115
|
if (e.key !== "Tab" || !panel.current) return;
|
|
98
|
-
const items =
|
|
116
|
+
const items = tabbablesIn(panel.current);
|
|
99
117
|
if (items.length === 0) {
|
|
100
118
|
e.preventDefault();
|
|
101
119
|
panel.current.focus();
|
|
@@ -112,7 +130,7 @@ function Modal({ open, title, onClose, footer, children }) {
|
|
|
112
130
|
if (!e.shiftKey && active === last) {
|
|
113
131
|
e.preventDefault();
|
|
114
132
|
first.focus();
|
|
115
|
-
} else if (e.shiftKey && active === first) {
|
|
133
|
+
} else if (e.shiftKey && (active === first || active === panel.current)) {
|
|
116
134
|
e.preventDefault();
|
|
117
135
|
last.focus();
|
|
118
136
|
}
|
|
@@ -133,16 +151,13 @@ function Modal({ open, title, onClose, footer, children }) {
|
|
|
133
151
|
}, [open]);
|
|
134
152
|
useEffect(() => {
|
|
135
153
|
if (!open) return;
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
) || panel.current;
|
|
154
|
+
const body = panel.current?.querySelector(".rx-modal__body");
|
|
155
|
+
const target = (body ? tabbablesIn(body) : [])[0] || panel.current;
|
|
139
156
|
target?.focus();
|
|
140
157
|
}, [open]);
|
|
141
158
|
if (!open) return null;
|
|
142
159
|
return createPortal(
|
|
143
|
-
/* @__PURE__ */ jsx5("div", { className: "rx-scrim", onMouseDown: (
|
|
144
|
-
if (e.target === e.currentTarget) onClose();
|
|
145
|
-
}, children: /* @__PURE__ */ jsxs3("div", { className: "rx-modal", role: "dialog", "aria-modal": "true", "aria-label": title, tabIndex: -1, ref: panel, children: [
|
|
160
|
+
/* @__PURE__ */ jsx5("div", { className: "rx-scrim", onMouseDown: dismissOnScrim(onClose), children: /* @__PURE__ */ jsxs3("div", { className: "rx-modal", role: "dialog", "aria-modal": "true", "aria-label": title, tabIndex: -1, ref: panel, children: [
|
|
146
161
|
/* @__PURE__ */ jsxs3("div", { className: "rx-modal__head", children: [
|
|
147
162
|
/* @__PURE__ */ jsx5("div", { className: "rx-modal__title", children: title }),
|
|
148
163
|
/* @__PURE__ */ jsx5(Button, { variant: "ghost", size: "sm", iconOnly: true, icon: "x", "aria-label": "Close", onClick: onClose })
|
package/src/styles/callout.css
CHANGED
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
.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; }
|
|
84
84
|
.ui-toast__action:hover { background: color-mix(in srgb, var(--bg) 55%, transparent); }
|
|
85
85
|
|
|
86
|
-
.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; position: relative; }
|
|
86
|
+
.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; position: relative; font: inherit; }
|
|
87
87
|
.ui-toast__close svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 2.5; }
|
|
88
88
|
/* The target, not the ink. WCAG 2.5.8 measures what a pointer can land on, and
|
|
89
89
|
the drawn button is 19x19 — a 15px glyph in 2px of padding. Growing the box
|
package/src/styles/card.css
CHANGED
|
@@ -43,6 +43,13 @@
|
|
|
43
43
|
/* Interactive card (link / button semantics) */
|
|
44
44
|
.ui-card--interactive {
|
|
45
45
|
cursor: pointer;
|
|
46
|
+
/* Written as a <button>, the card would take the browser's frame, centred text
|
|
47
|
+
and `font: 400 13.3333px Arial` — a shorthand, so `font: inherit` and not the
|
|
48
|
+
family longhand. .ui-card already states its own background, and the light
|
|
49
|
+
theme's border rule is (0,3,0) so `border: 0` here cannot reach it. #251 */
|
|
50
|
+
border: 0;
|
|
51
|
+
text-align: left;
|
|
52
|
+
font: inherit;
|
|
46
53
|
transition: transform var(--dur-fast) var(--ease), box-shadow var(--dur-med) var(--ease);
|
|
47
54
|
}
|
|
48
55
|
.ui-card--interactive:hover {
|
package/src/styles/drawer.css
CHANGED
package/src/styles/dropdown.css
CHANGED
|
@@ -102,15 +102,27 @@
|
|
|
102
102
|
}
|
|
103
103
|
.ui-dropdown__panel--portal.is-open { opacity: 1; visibility: visible; transform: translateY(0); }
|
|
104
104
|
|
|
105
|
-
/* Item row — [icon] [main: label + desc] [badge] [tick]
|
|
105
|
+
/* Item row — [icon] [main: label + desc] [badge] [tick]. A row is a <div>, an
|
|
106
|
+
<a href> or a <button>: `is-selected` and __tick below mean a row gets
|
|
107
|
+
chosen, and choosing is a button's job. So the tag's own styling is reset
|
|
108
|
+
here, the way .ui-nav__item resets it, and the three render identically.
|
|
109
|
+
why: docs/specification.md#a-dropdown-row-is-a-div-a-link-or-a-button */
|
|
106
110
|
.ui-dropdown__item {
|
|
107
111
|
display: flex;
|
|
108
112
|
gap: 11px;
|
|
109
113
|
align-items: center;
|
|
114
|
+
width: 100%;
|
|
110
115
|
padding: 9px 12px;
|
|
116
|
+
border: 0;
|
|
111
117
|
border-radius: var(--radius-sm);
|
|
118
|
+
background: none;
|
|
112
119
|
cursor: pointer;
|
|
120
|
+
text-align: left;
|
|
113
121
|
text-decoration: none;
|
|
122
|
+
/* `font`, not `font-family`: the UA writes one shorthand, `font: 400 13.3333px
|
|
123
|
+
Arial`, so answering its family alone leaves the size and the line-height
|
|
124
|
+
standing. The same answer .ui-toast__action gives. */
|
|
125
|
+
font: inherit;
|
|
114
126
|
color: var(--text);
|
|
115
127
|
transition: background var(--dur-fast) var(--ease);
|
|
116
128
|
}
|
package/src/styles/feedback.css
CHANGED
|
@@ -22,6 +22,16 @@
|
|
|
22
22
|
transform: translate(-50%, -100%) translateY(-4px) scale(.82); transform-origin: bottom center;
|
|
23
23
|
display: inline-flex; align-items: center; gap: 9px;
|
|
24
24
|
background: var(--ui-fb-pill-grad); color: var(--accent-contrast);
|
|
25
|
+
/* The pill is a click target the kit renders as a bare <div>, so the consumer who
|
|
26
|
+
needs it from the keyboard writes it as a <button> — which brings a 2px outset
|
|
27
|
+
frame around the gradient, a centred line and `font: 400 13.3333px Arial`. Stated
|
|
28
|
+
here rather than taken from the element. `font` and not `font-family`, because
|
|
29
|
+
Chrome's rule is the shorthand; the three longhands after it keep what the pill
|
|
30
|
+
wants and only the leading comes from the shorthand, which is the 1.62 the pill
|
|
31
|
+
already inherits as a div. `text-align` paints nothing on an inline-flex box
|
|
32
|
+
either way and is declared because leaving it to the element is the dependency
|
|
33
|
+
this refuses. why: #251 */
|
|
34
|
+
border: 0; text-align: center; font: inherit;
|
|
25
35
|
font-family: var(--font-sans); font-size: 13px; font-weight: var(--weight-semibold); letter-spacing: .01em;
|
|
26
36
|
padding: 9px 17px 9px 15px; border-radius: 999px; cursor: pointer; white-space: nowrap; overflow: hidden;
|
|
27
37
|
box-shadow: 0 10px 26px var(--ui-fb-pill-glow),
|
|
@@ -68,7 +78,7 @@
|
|
|
68
78
|
.ui-fbc__head { display: flex; align-items: center; gap: 12px; padding: 17px 20px 0; }
|
|
69
79
|
.ui-fbc__chip { display: inline-flex; align-items: center; gap: 8px; color: var(--accent); font-size: 13px; font-weight: var(--weight-semibold); letter-spacing: .01em; white-space: nowrap; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
70
80
|
.ui-fbc__chip svg { width: 14px; height: 14px; flex: none; opacity: .85; stroke-width: 2.7; }
|
|
71
|
-
.ui-fbc__x { margin-left: auto; background: var(--surface-2); color: var(--dim); border: 0; width: 28px; height: 28px; border-radius: 8px; cursor: pointer; display: grid; place-items: center; flex: none; }
|
|
81
|
+
.ui-fbc__x { margin-left: auto; background: var(--surface-2); color: var(--dim); border: 0; width: 28px; height: 28px; border-radius: 8px; cursor: pointer; display: grid; place-items: center; flex: none; font: inherit; }
|
|
72
82
|
.ui-fbc__x:hover { color: var(--strong); }
|
|
73
83
|
.ui-fbc__x svg { width: 14px; height: 14px; stroke-width: 2.7; }
|
|
74
84
|
.ui-fbc__quote { margin: 14px 20px 0; background: var(--surface-2); border-radius: 12px; padding: 12px 15px; display: flex; gap: 11px; }
|
package/src/styles/topbar.css
CHANGED
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
color: var(--text);
|
|
80
80
|
border: 1px solid var(--border);
|
|
81
81
|
border-radius: 10px;
|
|
82
|
+
font: inherit;
|
|
82
83
|
transition:
|
|
83
84
|
transform var(--dur-fast) var(--ease),
|
|
84
85
|
background var(--dur-fast) var(--ease),
|
|
@@ -146,6 +147,17 @@
|
|
|
146
147
|
padding: 10px 13px;
|
|
147
148
|
border-radius: 10px;
|
|
148
149
|
cursor: pointer;
|
|
150
|
+
/* What a browser paints on a <button> before any author rule runs. A consumer
|
|
151
|
+
reaches for <button> whenever the row has to work from the keyboard, so the
|
|
152
|
+
row states these rather than taking them from the element. `font` and not
|
|
153
|
+
`font-family`: Chrome's rule is the shorthand `font: 400 13.3333px Arial`,
|
|
154
|
+
and answering the family alone leaves a row 2.25px shorter in type 1.17px
|
|
155
|
+
smaller. Any font longhand a rule keeps goes after it. why: #251 */
|
|
156
|
+
width: 100%;
|
|
157
|
+
background: none;
|
|
158
|
+
border: 0;
|
|
159
|
+
text-align: left;
|
|
160
|
+
font: inherit;
|
|
149
161
|
transition: background var(--dur-fast) var(--ease);
|
|
150
162
|
}
|
|
151
163
|
.vopt:hover { background: var(--surface); }
|
|
@@ -178,6 +190,7 @@
|
|
|
178
190
|
border: 1px solid var(--border);
|
|
179
191
|
background: linear-gradient(145deg, var(--accent-strong), var(--purple));
|
|
180
192
|
color: var(--accent-contrast);
|
|
193
|
+
font: inherit;
|
|
181
194
|
font-weight: 600;
|
|
182
195
|
font-size: 12.5px;
|
|
183
196
|
display: grid;
|