feat(design): the field family, the dropdown trigger, native controls, the no-raw-input lint rule (E28 S3c)
- ui/Input, Textarea, InnerInput + fieldClass (A4): the >= 3:1 edge, a 2px accent focus edge, the danger edge for invalid, md/sm/xs/heading rungs, 1rem and 44 on touch - ui/SelectTrigger (A5): SortControl, FilterSelect, the view switcher and the date filter on one trigger, a pill in a toolbar, r8 in a form - ui/NativeControls (A6): Checkbox, CheckRow, Slider, ColorInput - every raw input swept (~100 sites); inputCls retired; local/input-primitive - useReturnFocus: an in-place editor hands the focus back to its trigger on Enter/Escape (the playlist and Plex title renames, the priority number); the Plex playlist rename gains Escape and an accessible name - back to top: a round 48 FAB (owner decision); DESIGN.md A2 and the log
This commit is contained in:
@@ -100,8 +100,9 @@ const BUTTON_PRIMITIVES = new Set([
|
||||
"form.tsx", // Switch
|
||||
"PriorityStepper.tsx",
|
||||
"SortControl.tsx",
|
||||
"FilterSelect.tsx", // the dropdown trigger (A5, S3c)
|
||||
"Combobox.tsx", // the field's own chips and chevron (A4, S3c)
|
||||
"SelectTrigger.tsx", // the dropdown trigger (A5)
|
||||
"FilterSelect.tsx", // its option rows
|
||||
"Combobox.tsx", // the field's own chips and chevron (A4)
|
||||
]);
|
||||
const buttonPrimitive = {
|
||||
meta: {
|
||||
@@ -124,6 +125,40 @@ const buttonPrimitive = {
|
||||
};
|
||||
},
|
||||
};
|
||||
// The same for a text field and the native checkbox / slider / colour well (docs/DESIGN.md A4, A6;
|
||||
// E28 S3c): ~100 hand-styled boxes carried six edges, four radii and two focus looks between them. A
|
||||
// file picker has no look (it is hidden behind a Button), so it passes.
|
||||
const INPUT_PRIMITIVES = new Set([
|
||||
"Input.tsx",
|
||||
"NativeControls.tsx",
|
||||
"PriorityStepper.tsx", // the number box laid over the stepper's own face
|
||||
]);
|
||||
const inputPrimitive = {
|
||||
meta: {
|
||||
type: "problem",
|
||||
messages: {
|
||||
raw: "A raw <{{name}}> outside the field primitives: use ui/Input, Textarea or InnerInput (inside a fieldClass frame), or Checkbox / CheckRow / Slider / ColorInput.",
|
||||
},
|
||||
},
|
||||
create(context) {
|
||||
const path = context.filename.split("\\").join("/");
|
||||
const file = path.slice(path.lastIndexOf("/") + 1);
|
||||
if (path.includes("/src/components/ui/") && INPUT_PRIMITIVES.has(file)) return {};
|
||||
return {
|
||||
JSXOpeningElement(node) {
|
||||
if (node.name.type !== "JSXIdentifier") return;
|
||||
const name = node.name.name;
|
||||
if (name !== "input" && name !== "textarea") return;
|
||||
const type = node.attributes.find(
|
||||
(a) => a.type === "JSXAttribute" && a.name.name === "type",
|
||||
);
|
||||
const v = type?.value?.type === "Literal" ? type.value.value : null;
|
||||
if (name === "input" && (v === "file" || v === "hidden")) return;
|
||||
context.report({ node, messageId: "raw", data: { name } });
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
// The words a screen reader or a tooltip says are user-facing text like any other, and the HU/EN
|
||||
// parity rule covers them too. Five `aria-label="remove"`-style literals survived every lane (the
|
||||
// Plex playlist rows, a filter chip, the player's reset) and a whole opt-in panel was English only —
|
||||
@@ -200,6 +235,7 @@ const localRules = {
|
||||
"jsx-comment-text": jsxCommentText,
|
||||
"focus-safe-disabled": focusSafeDisabled,
|
||||
"button-primitive": buttonPrimitive,
|
||||
"input-primitive": inputPrimitive,
|
||||
"literal-a11y-text": literalA11yText,
|
||||
},
|
||||
};
|
||||
@@ -255,6 +291,7 @@ export default tseslint.config(
|
||||
"local/jsx-comment-text": "error",
|
||||
"local/focus-safe-disabled": "error",
|
||||
"local/button-primitive": "error",
|
||||
"local/input-primitive": "error",
|
||||
"local/literal-a11y-text": "error",
|
||||
"local/sortable-a11y": "error",
|
||||
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
|
||||
@@ -418,6 +455,7 @@ export default tseslint.config(
|
||||
"local/literal-a11y-text": "off",
|
||||
// A test renders a raw button as a stand-in trigger or a focus target, not as product UI.
|
||||
"local/button-primitive": "off",
|
||||
"local/input-primitive": "off",
|
||||
},
|
||||
},
|
||||
prettier,
|
||||
|
||||
Reference in New Issue
Block a user