create-bestax 4.2.16 → 4.2.18
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 +13 -5
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +7 -2
- package/dist/display.d.ts +9 -0
- package/dist/display.d.ts.map +1 -1
- package/dist/display.js +19 -1
- package/dist/file-system.d.ts +16 -3
- package/dist/file-system.d.ts.map +1 -1
- package/dist/file-system.js +41 -5
- package/dist/project-creator.d.ts +16 -1
- package/dist/project-creator.d.ts.map +1 -1
- package/dist/project-creator.js +35 -6
- package/dist/prompts.d.ts +1 -0
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +3 -2
- package/dist/validators.d.ts +8 -0
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +18 -1
- package/package.json +7 -7
- package/templates/skills/bestax-custom-component/SKILL.md +36 -1
- package/templates/skills/bestax-custom-component/references/api.md +36 -14
- package/templates/skills/bestax-custom-component/references/component-catalog.md +10 -2
- package/templates/skills/bestax-custom-component/references/library-contributor.md +4 -1
- package/templates/skills/bestax-form/SKILL.md +46 -30
- package/templates/skills/bestax-form/references/api.md +15 -10
- package/templates/skills/bestax-form/references/patterns.md +11 -6
- package/templates/skills/bestax-icons/SKILL.md +8 -1
- package/templates/skills/bestax-icons/references/icon-libraries.md +35 -2
- package/templates/skills/bestax-layout-scaffold/SKILL.md +54 -4
- package/templates/skills/bestax-layout-scaffold/references/archetypes.md +57 -0
- package/templates/skills/bestax-layout-scaffold/references/layout-components.md +3 -3
- package/templates/skills/bestax-migrate/SKILL.md +23 -2
- package/templates/skills/bestax-migrate/references/bloomer/prop-map.md +34 -8
- package/templates/skills/bestax-migrate/references/bloomer/unmappables.md +18 -0
- package/templates/skills/bestax-migrate/references/bulma-classes/component-map.md +9 -4
- package/templates/skills/bestax-migrate/references/bulma-classes/prop-map.md +1 -1
- package/templates/skills/bestax-migrate/references/bulma-classes/unmappables.md +317 -4
- package/templates/skills/bestax-migrate/references/css-migration.md +5 -1
- package/templates/skills/bestax-migrate/references/rbx/prop-map.md +31 -7
- package/templates/skills/bestax-migrate/references/rbx/unmappables.md +41 -14
- package/templates/skills/bestax-migrate/references/react-bulma-components/component-map.md +3 -3
- package/templates/skills/bestax-migrate/references/react-bulma-components/prop-map.md +22 -0
- package/templates/skills/bestax-migrate/references/react-bulma-components/unmappables.md +33 -8
- package/templates/skills/bestax-optimize/SKILL.md +2 -0
- package/templates/skills/bestax-optimize/references/modular-build.md +23 -12
- package/templates/skills/bestax-theming/SKILL.md +4 -1
- package/templates/skills/bestax-theming/references/css-variables.md +130 -2
- package/templates/skills/bestax-theming/references/themeable-components.md +8 -2
- package/templates/vite-ts/package.json +1 -0
- package/templates/vite-ts/tsconfig.app.json +10 -9
- package/templates/vite-ts/tsconfig.node.json +15 -5
- package/templates/vite-ts/src/vite-env.d.ts +0 -1
|
@@ -63,10 +63,45 @@ Wrap it in the library's `ClientOnly` rather than checking `typeof window`: it r
|
|
|
63
63
|
until the page has hydrated, so the server and client markup match. `references/api.md` has the
|
|
64
64
|
details.
|
|
65
65
|
|
|
66
|
+
A button that turns something on or off and stays that way (Bold, Mute, Show archived) is
|
|
67
|
+
`ToggleButton`: use it rather than `Button isActive`, which only draws a shade and sets no
|
|
68
|
+
`aria-pressed`. It's always `type="button"`, holds its own state (`defaultPressed`) or takes
|
|
69
|
+
`pressed` with `onPressedChange`, and fills with `color` (default `link`) only while pressed.
|
|
70
|
+
Its label stays the same in both states, and an icon-only one needs an `aria-label`. A set goes
|
|
71
|
+
in `Buttons hasAddons role="group" aria-label="…"`, where it's also `Buttons.ToggleButton`. A row
|
|
72
|
+
where only one can be on is a radio group, so it's `SegmentedControl` (below), never toggles that
|
|
73
|
+
switch each other off. A setting saved with a form is a `Switch` or a `Checkbox`.
|
|
74
|
+
|
|
75
|
+
A row of buttons where only one can be on (text alignment, a list or grid view, monthly or yearly
|
|
76
|
+
billing) is `SegmentedControl`: use it rather than `Buttons hasAddons` with `isActive` or a fill
|
|
77
|
+
you move yourself. Give it an `aria-label`, put one `SegmentedControl.Segment value="…"` inside per
|
|
78
|
+
option, and hold the choice with `defaultValue`, or `value` with `onChange(value)`. A screen reader
|
|
79
|
+
hears a radio group, and the single tab stop, the arrow keys, Home and End come built in, so add
|
|
80
|
+
no `onKeyDown` or `role` of your own. An icon-only segment needs its own `aria-label`, as an
|
|
81
|
+
icon-only `ToggleButton` does. The arrow keys check the segment they land on, so `onChange` fires
|
|
82
|
+
on each, and pressing the checked segment keeps it checked. It carries no form value: a choice
|
|
83
|
+
the form submits is `Radios`.
|
|
84
|
+
|
|
66
85
|
A panel opened from a button (a filter form, share options, an inline edit) is `Popover`: use
|
|
67
86
|
it rather than building one. It anchors the panel, moves focus in and back, and closes on
|
|
68
87
|
Escape and outside presses.
|
|
69
88
|
|
|
89
|
+
A preview that shows up on hover (a profile card on a username, a summary on a link) is
|
|
90
|
+
`HoverCard`: use it rather than wiring pointer events to a `Popover`. It opens on hover and
|
|
91
|
+
keyboard focus after a delay, lets the pointer move onto the card, closes on Escape, and ignores
|
|
92
|
+
touch unless `openOnTap` is on, which suits only a trigger with no tap action of its own (a
|
|
93
|
+
`Button` with no `onClick`, never a link). Its `trigger` has to be a link or a button, and a trigger in running text takes
|
|
94
|
+
`appendToBody`. A form never goes in one; that's `Popover`.
|
|
95
|
+
|
|
96
|
+
A menu of actions that opens on a right-click (Rename and Delete on a table row, Archive on a
|
|
97
|
+
card) is `ContextMenu`: use it rather than an `onContextMenu` handler that positions a
|
|
98
|
+
`Dropdown`. It opens at the point, also on Shift+F10, the context menu key and a long press,
|
|
99
|
+
and its items are `ContextMenu.Item` and `ContextMenu.Divider`, the same components as
|
|
100
|
+
`Dropdown.Item` and `Dropdown.Divider`. Its `trigger` is cloned, not wrapped, so a `Table.Tr`
|
|
101
|
+
works, and the menu needs `aria-label`. Put the same items in a visible `Dropdown` as well:
|
|
102
|
+
nothing tells a screen reader user a context menu is there, and on macOS only VoiceOver opens
|
|
103
|
+
it from the keyboard.
|
|
104
|
+
|
|
70
105
|
Building something else that floats (a command palette)? Render it through the library's
|
|
71
106
|
`Portal` rather than `createPortal`: it renders nothing on the server and during hydration, so
|
|
72
107
|
the server and client markup match. `references/api.md` has the details.
|
|
@@ -139,7 +174,7 @@ the children with `gap`. Before writing `style={{ … }}` anywhere, translate ea
|
|
|
139
174
|
| `textAlign: 'center'` | `textAlign="centered"` (also `left`, `right`, `justified`) |
|
|
140
175
|
| `color: '#…'` | `textColor` with the nearest Bulma color: `primary`, `link`, `info`, `success`, `warning`, `danger`, `white`, `black`, `grey` (+ `grey-light`, `grey-dark`, …) |
|
|
141
176
|
| `backgroundColor: '#…'` | `bgColor` (same palette) |
|
|
142
|
-
| `fontSize: …` | `textSize="1"`…`"7"` (`1` largest)
|
|
177
|
+
| `fontSize: …` | `textSize="1"`…`"7"` (`1` largest). On `Title`/`SubTitle`, `as` sets the level and `size` picks it too, so an `<h3>` at size 4 is `as="h3" textSize="4"` |
|
|
143
178
|
| `fontWeight: …` | `textWeight`: `light`, `normal`, `medium`, `semibold`, `bold` |
|
|
144
179
|
| `textTransform`, italics | `textTransform`: `uppercase`, `lowercase`, `capitalized`, `italic` |
|
|
145
180
|
| `display: 'flex'` + flex properties | same-named props: `display="flex"`, `flexDirection`, `justifyContent`, `alignItems`, `flexWrap` |
|
|
@@ -88,7 +88,8 @@ union (a compile error beats a silent no-op).
|
|
|
88
88
|
|
|
89
89
|
## `ConfigProvider` / `Theme` — `helpers/Config.tsx`, `helpers/Theme.tsx`
|
|
90
90
|
|
|
91
|
-
`ConfigProvider` provides the runtime `classPrefix` (and `iconLibrary`
|
|
91
|
+
`ConfigProvider` provides the runtime `classPrefix` (and `iconLibrary` and `portalContainer`)
|
|
92
|
+
consumed via `useConfig`;
|
|
92
93
|
`classPrefix` feeds `usePrefixedClassNames` (opt-in class prefixing to avoid collisions). `Theme`
|
|
93
94
|
overrides `--bulma-*` custom properties at runtime —
|
|
94
95
|
which is exactly why component SCSS must register its vars via `cv.register-vars` rather than
|
|
@@ -114,17 +115,23 @@ hard-coding values.
|
|
|
114
115
|
## Floating content: `Portal`
|
|
115
116
|
|
|
116
117
|
A panel anchored to the button that opens it is `Popover`, which already portals
|
|
117
|
-
(`appendToBody`), traps focus and dismisses itself
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
118
|
+
(`appendToBody`), traps focus and dismisses itself, a preview that opens on hover is
|
|
119
|
+
`HoverCard`, which portals the same way, and a menu that opens at a right-click or a long press
|
|
120
|
+
is `ContextMenu`, which always portals and places itself at the point. `Portal` is for the
|
|
121
|
+
overlays they don't cover.
|
|
122
|
+
|
|
123
|
+
`<Portal container?>` (`helpers/portal.tsx`) renders its children into `container` (an element or
|
|
124
|
+
a selector), or without one into the `portalContainer` a `ConfigProvider` sets, and
|
|
125
|
+
`document.body` when none is set, so floating content escapes an ancestor's `overflow`,
|
|
122
126
|
`transform` or stacking context. It renders nothing on the server and during hydration, so the
|
|
123
127
|
first client render matches; `disabled` renders in place instead. Use it rather than calling
|
|
124
128
|
`createPortal` yourself, which React's server renderer can't render.
|
|
125
129
|
|
|
126
|
-
Focus follows the DOM, not the React tree: portaled content
|
|
127
|
-
|
|
130
|
+
Focus follows the DOM, not the React tree: portaled content sits in the Tab order where its
|
|
131
|
+
container is, which for the default `document.body` is last, so an overlay you build that takes
|
|
132
|
+
focus moves it in when it opens and back to its trigger when it closes. A `HoverCard` moves no
|
|
133
|
+
focus when it opens, and a `ContextMenu` moves it to its first item and closes on Tab, handing
|
|
134
|
+
focus back. To trap portaled content,
|
|
128
135
|
put the trap's ref on the element inside the `Portal`, as below with
|
|
129
136
|
`useFocusTrap(panelRef, { active: open, restoreFocus: buttonRef })`. A focus trap doesn't cover
|
|
130
137
|
what a `Portal` inside it renders, so render nested overlays inside the trapped element.
|
|
@@ -141,17 +148,28 @@ what a `Portal` inside it renders, so render nested overlays inside the trapped
|
|
|
141
148
|
}
|
|
142
149
|
```
|
|
143
150
|
|
|
151
|
+
Content in `document.body` loses a scoped `Theme`'s variables and a shadow root's styles. In a web
|
|
152
|
+
component or a themed section, wrap the area in a `ConfigProvider` whose `portalContainer` is an
|
|
153
|
+
element inside it, which also covers what the library's components portal, such as `Popover`'s
|
|
154
|
+
`appendToBody` panel or a `Modal` with `portal`. In a shadow root, pass the
|
|
155
|
+
element itself, held in state from a ref callback, since a selector can't reach inside one. What
|
|
156
|
+
moves there is `position: fixed`, so the element must not sit under an ancestor with a
|
|
157
|
+
`transform`, `filter`, `backdrop-filter`, `perspective`, `will-change: transform`, or a `contain`
|
|
158
|
+
that includes `paint` or `layout`: that ancestor becomes the containing block, and overlays and
|
|
159
|
+
anchored panels land against it instead of the viewport.
|
|
160
|
+
|
|
144
161
|
## Holding focus: `useFocusTrap`
|
|
145
162
|
|
|
146
163
|
`useFocusTrap(ref, { active, initialFocusRef, restoreFocus })` (`helpers/useFocusTrap.ts`) moves
|
|
147
164
|
focus into `ref` when `active` turns on, wraps Tab at the first and last tab stops (the ones the
|
|
148
165
|
browser visits, so hidden, disabled, inert and `tabIndex={-1}` elements are skipped and a radio
|
|
149
166
|
group counts once) and restores focus when it turns off. It handles Tab only: wire Escape to
|
|
150
|
-
close
|
|
151
|
-
the
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
167
|
+
close, and call `preventDefault()` on the Escape it closes on, so a `Modal` or `Sidebar` around
|
|
168
|
+
the panel stays open for the next one. Pass the trigger's ref as `restoreFocus` for a panel
|
|
169
|
+
opened from a button. It looks for the container again each time the component calling it
|
|
170
|
+
renders, so `active` can follow `open` alone even when the panel mounts a render later, as
|
|
171
|
+
portaled content can. The example below shows the hook's wiring; for a real filter panel opened
|
|
172
|
+
from a button, use `Popover`, which does all of this.
|
|
155
173
|
|
|
156
174
|
```tsx
|
|
157
175
|
function FilterPanel({ children }: { children: React.ReactNode }) {
|
|
@@ -176,7 +194,11 @@ function FilterPanel({ children }: { children: React.ReactNode }) {
|
|
|
176
194
|
role="dialog"
|
|
177
195
|
aria-label="Filters"
|
|
178
196
|
tabIndex={-1}
|
|
179
|
-
onKeyDown={e =>
|
|
197
|
+
onKeyDown={e => {
|
|
198
|
+
if (e.key !== 'Escape') return;
|
|
199
|
+
e.preventDefault();
|
|
200
|
+
setOpen(false);
|
|
201
|
+
}}
|
|
180
202
|
>
|
|
181
203
|
{children}
|
|
182
204
|
</div>
|
|
@@ -37,7 +37,7 @@ instead of hand-writing markup.
|
|
|
37
37
|
- Raw `*Base` form exports (`InputBase`, `SelectBase`, `TextAreaBase`, …) are
|
|
38
38
|
escape-hatch variants of the convenience wrappers above them; see the Form docs.
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
102 documented components. Generated from the API docs — every exported
|
|
41
41
|
component is guaranteed to appear (the generator fails if one lacks an API page).
|
|
42
42
|
|
|
43
43
|
## Elements
|
|
@@ -55,6 +55,7 @@ component is guaranteed to appear (the generator fails if one lacks an API page)
|
|
|
55
55
|
- [Icon](https://bestax.io/docs/api/elements/icon) — The `Icon` component is a Bulma-styled wrapper for displaying icons from various libraries (Font Awesome, Material Design Icons, Ionicons…
|
|
56
56
|
- [IconText](https://bestax.io/docs/api/elements/icontext) — The `IconText` component provides a Bulma-styled horizontal arrangement of one or more `Icon` components and optional text.
|
|
57
57
|
- [Image](https://bestax.io/docs/api/elements/image) — The `Image` component wraps images, iframes, or custom content in a Bulma-styled container, supporting fixed sizes, aspect ratios, rounded corners…
|
|
58
|
+
- [Kbd](https://bestax.io/docs/api/elements/kbd) — The `Kbd` component renders a `<kbd>` drawn as a keycap, for a key the reader presses.
|
|
58
59
|
- [Link](https://bestax.io/docs/api/elements/link) — The `Link` component renders a styled anchor (`<a>`) element with Bulma helper class integration.
|
|
59
60
|
- [LinkButton](https://bestax.io/docs/api/elements/linkbutton) — The `LinkButton` component renders a `<button>` that visually looks like text or a link.
|
|
60
61
|
- [ListItem](https://bestax.io/docs/api/elements/listitem) — The `ListItem` component renders a styled list item (`<li>`) element with Bulma helper class integration.
|
|
@@ -72,6 +73,7 @@ component is guaranteed to appear (the generator fails if one lacks an API page)
|
|
|
72
73
|
- [Tag](https://bestax.io/docs/api/elements/tag) — The `Tag` component renders a Bulma-styled label or badge.
|
|
73
74
|
- [Tags](https://bestax.io/docs/api/elements/tags) — The `Tags` component groups multiple `Tag` components together in a Bulma-styled container that wraps onto multiple lines by default.
|
|
74
75
|
- [Title](https://bestax.io/docs/api/elements/title) — The `Title` component renders a Bulma-styled title (heading), supporting sizes `1`-`6`, spacing, and rendering as any heading or paragraph element (`h1`-`h6`…
|
|
76
|
+
- [ToggleButton](https://bestax.io/docs/api/elements/togglebutton) — The `ToggleButton` component renders a button that stays pressed until it's pressed again, and says whether it's on through `aria-pressed`.
|
|
75
77
|
- [UnorderedList](https://bestax.io/docs/api/elements/unorderedlist) — The `UnorderedList` component renders a styled unordered list (`<ul>`) element with Bulma helper class integration.
|
|
76
78
|
|
|
77
79
|
## Components
|
|
@@ -84,8 +86,11 @@ component is guaranteed to appear (the generator fails if one lacks an API page)
|
|
|
84
86
|
- [Carousel](https://bestax.io/docs/api/components/carousel) — The `Carousel` component provides an image/content slider with navigation arrows and indicators.
|
|
85
87
|
- [Collapse](https://bestax.io/docs/api/components/collapse) — The `Collapse` component provides an expandable/collapsible content panel.
|
|
86
88
|
- [Collapses](https://bestax.io/docs/api/components/collapses) — The `Collapses` component groups `Collapse` items into an accordion that keeps one item open at a time, or any number with `multiple`.
|
|
89
|
+
- [ContextMenu](https://bestax.io/docs/api/components/contextmenu) — The `ContextMenu` component opens a menu of actions where you right-click an element, such as a table row or a card, or long press it on a touch screen.
|
|
87
90
|
- [Dialog](https://bestax.io/docs/api/components/dialog) — The `Dialog` component provides ready-made confirm and alert dialogs, so a destructive action stays one `await dialog.confirm()` call away.
|
|
88
91
|
- [Dropdown](https://bestax.io/docs/api/components/dropdown) — The `Dropdown` component provides Bulma's versatile dropdown menu for your Bulma React UI.
|
|
92
|
+
- [EmptyState](https://bestax.io/docs/api/components/emptystate) — The `EmptyState` component renders the block an app shows where content would be: an empty list, a search with no results, or a first-run screen.
|
|
93
|
+
- [HoverCard](https://bestax.io/docs/api/components/hovercard) — The `HoverCard` component shows a card previewing what's behind a link or a button while the pointer rests on it or keyboard focus is on it.
|
|
89
94
|
- [Loading](https://bestax.io/docs/api/components/loading) — The `Loading` component provides a loading overlay with a spinner animation.
|
|
90
95
|
- [Menu](https://bestax.io/docs/api/components/menu) — The `Menu` component provides Bulma's vertical navigation menu: a simple, accessible sidebar or section menu for your Bulma React UI.
|
|
91
96
|
- [Message](https://bestax.io/docs/api/components/message) — The `Message` component provides Bulma's flexible notice/message box for your Bulma React UI.
|
|
@@ -95,11 +100,14 @@ component is guaranteed to appear (the generator fails if one lacks an API page)
|
|
|
95
100
|
- [Panel](https://bestax.io/docs/api/components/panel) — The `Panel` component implements Bulma's versatile panel block for React.
|
|
96
101
|
- [Popover](https://bestax.io/docs/api/components/popover) — The `Popover` component opens a panel of interactive content, such as a filter form or a set of share options, anchored to the element that toggles it.
|
|
97
102
|
- [Reveal](https://bestax.io/docs/api/components/reveal) — The `Reveal` component animates its content into view as it scrolls into the viewport, backed by `IntersectionObserver`.
|
|
103
|
+
- [SegmentedControl](https://bestax.io/docs/api/components/segmentedcontrol) — The `SegmentedControl` component renders a row of buttons where only one can be on, and tells a screen reader it's a radio group.
|
|
98
104
|
- [Sidebar](https://bestax.io/docs/api/components/sidebar) — The `Sidebar` component provides a slide-out navigation panel that appears from the left or right side of the screen.
|
|
99
105
|
- [Steps](https://bestax.io/docs/api/components/steps) — The `Steps` component provides a multi-step progress indicator for wizard flows, checkout processes, or any multi-step workflow.
|
|
100
106
|
- [Tabs](https://bestax.io/docs/api/components/tabs) — The `Tabs` component provides flexible and fully-featured Bulma tab navigation for your Bulma React UI.
|
|
107
|
+
- [Timeline](https://bestax.io/docs/api/components/timeline) — The `Timeline` component renders a list of events in the order they happened, each with a marker on a line down the side.
|
|
101
108
|
- [Toast](https://bestax.io/docs/api/components/toast) — The `Toast` component provides brief notification messages with optional action and cancel buttons.
|
|
102
109
|
- [Tooltip](https://bestax.io/docs/api/components/tooltip) — The `Tooltip` component displays helpful information when users hover over or focus on an element.
|
|
110
|
+
- [TreeView](https://bestax.io/docs/api/components/treeview) — The `TreeView` component renders a tree of items you browse and pick from, the way a file explorer works: one tab stop, the arrow keys to move…
|
|
103
111
|
|
|
104
112
|
## Form
|
|
105
113
|
|
|
@@ -147,7 +155,7 @@ component is guaranteed to appear (the generator fails if one lacks an API page)
|
|
|
147
155
|
|
|
148
156
|
- [ClientOnly](https://bestax.io/docs/api/helpers/clientonly) — `ClientOnly` renders its children only in the browser, after hydration, and a `fallback` until then…
|
|
149
157
|
- [ConfigProvider](https://bestax.io/docs/api/helpers/config) — The `ConfigProvider` component provides a React context for configuring global settings across all Bulma UI components.
|
|
150
|
-
- [Portal](https://bestax.io/docs/api/helpers/portal) — `Portal` renders its children into another part of the page, `document.body` unless `container` says otherwise…
|
|
158
|
+
- [Portal](https://bestax.io/docs/api/helpers/portal) — `Portal` renders its children into another part of the page, `document.body` unless `container` or a `ConfigProvider` says otherwise…
|
|
151
159
|
- [Theme](https://bestax.io/docs/api/helpers/theme) — The `Theme` component provides a powerful way to customize Bulma's appearance using CSS custom properties (CSS variables).
|
|
152
160
|
- [Valid value constants](https://bestax.io/docs/api/helpers/valid-values) — The helper tuples enumerate the core accepted values for the shared Bulma helper props — public API for building prop types and validation.
|
|
153
161
|
- [classNames](https://bestax.io/docs/api/helpers/classnames) — `classNames` is a utility function for conditionally joining class names together.
|
|
@@ -168,7 +168,10 @@ Rules that keep components consistent:
|
|
|
168
168
|
place in the DOM uses `Portal` (`helpers/portal.tsx`) rather than its own `createPortal` and
|
|
169
169
|
`typeof document` check. `Portal` renders nothing on the server or during hydration, so the
|
|
170
170
|
first client render matches the server markup. Test that with `renderToString` plus
|
|
171
|
-
`hydrateRoot` and a `console.error` spy, as `portal.test.tsx` does.
|
|
171
|
+
`hydrateRoot` and a `console.error` spy, as `portal.test.tsx` does. A component that has to
|
|
172
|
+
call `createPortal` itself, as `Modal` does to render in place until hydration, resolves its
|
|
173
|
+
target with `usePortalContainer` from the same module, so the `portalContainer` a
|
|
174
|
+
`ConfigProvider` sets reaches it too. Add it to `src/__tests__/portal-container.test.tsx`.
|
|
172
175
|
|
|
173
176
|
- **Focus management builds on `useFocusTrap`.** A new component that holds focus while it is
|
|
174
177
|
open uses `useFocusTrap` (`helpers/useFocusTrap.ts`) rather than its own Tab handler, so it
|
|
@@ -65,7 +65,7 @@ using context (`useInsideField` / `useInsideControl`) to detect their surroundin
|
|
|
65
65
|
```tsx
|
|
66
66
|
<Field hasAddons>
|
|
67
67
|
<Control isExpanded>
|
|
68
|
-
<InputBase placeholder="Search" />
|
|
68
|
+
<InputBase placeholder="Search" aria-label="Search" />
|
|
69
69
|
</Control>
|
|
70
70
|
<Control>
|
|
71
71
|
<Button color="primary">Go</Button>
|
|
@@ -73,6 +73,10 @@ using context (`useInsideField` / `useInsideControl`) to detect their surroundin
|
|
|
73
73
|
</Field>
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
+
A `grouped` or `hasAddons` Field names none of its controls, and a placeholder disappears once
|
|
77
|
+
the user types, so give each input in the row an `aria-label`, or a visible label on an outer
|
|
78
|
+
`Field` (`references/patterns.md`, "Grouped controls and addons").
|
|
79
|
+
|
|
76
80
|
## Component inventory
|
|
77
81
|
|
|
78
82
|
All import from `@allxsmith/bestax-bulma`. Convenience components auto-wrap Field+Control;
|
|
@@ -125,30 +129,41 @@ Full-width is `isFullwidth` on every component that supports it (`Button`, `Link
|
|
|
125
129
|
spellings compile only where they historically existed: `isFullWidth` everywhere except
|
|
126
130
|
`Sidebar`, `fullwidth` on `Tabs` only, `fullWidth` on `Sidebar` only.
|
|
127
131
|
|
|
128
|
-
|
|
129
|
-
`
|
|
130
|
-
`
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
`
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
`
|
|
139
|
-
`
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
`
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
`
|
|
150
|
-
|
|
151
|
-
|
|
132
|
+
**Labels.** Default to the convenience `label` prop, with `horizontal` for a side-by-side row.
|
|
133
|
+
`Input`, `Select`, `TextArea`, `File`, `Numberinput`, `Slider`, `DateInput`, `TimeInput`,
|
|
134
|
+
`DateTimeInput`, `Autocomplete` and `Taginput` then render the `Field` and the label and wire
|
|
135
|
+
`htmlFor`/`id`, to your `id` when you pass one (`<Input label="Email" id="email" horizontal />`);
|
|
136
|
+
an explicit `labelProps={{ htmlFor }}` wins. The group inputs (`Checkboxes`, `Radios`, `Rate`,
|
|
137
|
+
`DateRangeInput`) name their `role="group"`/`"radiogroup"` wrapper through `aria-labelledby`
|
|
138
|
+
instead. Inside a `Field` a convenience input drops its `label`, and an `inline` date/time picker
|
|
139
|
+
has no visible input to wire.
|
|
140
|
+
|
|
141
|
+
Composed, a labeled `Field` names the one control it holds: a composed `InputBase`/`SelectBase`/
|
|
142
|
+
`TextAreaBase`, a composed `DateInputBase`/`TimeInputBase`/`DateTimeInputBase` that is not
|
|
143
|
+
`inline`, or any input above takes its generated id, and a group (a composed `DateRangeInputBase`
|
|
144
|
+
too, `inline` or not) points `aria-labelledby` at the label's own id. **A control with an `id` of
|
|
145
|
+
its own never takes that id**, so `<Field label="Email"><Input id="email" /></Field>` renders a
|
|
146
|
+
label that names nothing: leave the `id` off, or pass the same value as
|
|
147
|
+
`labelProps={{ htmlFor: 'email' }}`. Wire it that way too where the association is skipped, in a
|
|
148
|
+
`grouped`/`hasAddons` row and in a horizontal `Field` whose body holds an inner `Field` (a nested
|
|
149
|
+
`Field` starts its own scope). For a group there, put `labelProps={{ id, htmlFor: undefined }}` on
|
|
150
|
+
the outer `Field` and a matching `aria-labelledby` on the group; the `htmlFor: undefined` keeps an
|
|
151
|
+
unmatched `for` out of server-rendered HTML.
|
|
152
|
+
|
|
153
|
+
A `range` `Slider` points the `for` at its low thumb and starts both thumbs' names with the label
|
|
154
|
+
("Price range Minimum value"); its `ariaLabel={[low, high]}` replaces those names outright, so
|
|
155
|
+
leave it off when a label already names the Slider. Those thumbs and an `Autocomplete`'s or
|
|
156
|
+
`Taginput`'s suggestion list point `aria-labelledby` at the label's `id`, which a label wired by
|
|
157
|
+
hand has only when you pass one, `labelProps={{ htmlFor, id }}`; without it they keep their
|
|
158
|
+
fallback names ("Minimum value"/"Maximum value", "Suggestions"). A `<label htmlFor>` you put in
|
|
159
|
+
`Field.Label` yourself (`references/patterns.md`) names its control through the `for` alone, so
|
|
160
|
+
label a row holding either with the `Field`'s `label` prop. An `aria-label` or `aria-labelledby`
|
|
161
|
+
you give a group wins over any label, and a `Checkbox`, `Radio` or `Switch` takes nothing from a
|
|
162
|
+
`Field` on its own: its children name it, and a `Field`'s label names one only when you wire it as
|
|
163
|
+
you would any control with an `id` of its own, `labelProps={{ htmlFor }}` plus that control's
|
|
164
|
+
`id`. A labeled `Field` whose `for` nothing takes (a group, a `Checkbox`, `Radio` or `Switch`, an
|
|
165
|
+
input with an `id` of its own, your own markup) drops it once mounted, though server-rendered HTML
|
|
166
|
+
keeps it. `labelProps={{ htmlFor: undefined }}` opts out of the association entirely.
|
|
152
167
|
|
|
153
168
|
## Convenience vs composed
|
|
154
169
|
|
|
@@ -265,11 +280,12 @@ for the expected classes/states, and say plainly that the visual pass is still o
|
|
|
265
280
|
## Checklist
|
|
266
281
|
|
|
267
282
|
- [ ] Built from the shipped form components (no hand-rolled inputs / reinvented controls).
|
|
268
|
-
- [ ] Every label is programmatically associated
|
|
269
|
-
inputs, and `Field` + single-
|
|
270
|
-
`
|
|
271
|
-
multi-control `Field`'s controls
|
|
272
|
-
a `<label htmlFor>` matching
|
|
283
|
+
- [ ] Every label is programmatically associated: the convenience `label` prop, the group
|
|
284
|
+
inputs, and `Field` + single-control composition do it on their own. A composed labeled
|
|
285
|
+
`Field` names a control with an `id` of its own, or one in an inner `Field`, only when its
|
|
286
|
+
`labelProps={{ htmlFor }}` matches that `id`, and a multi-control `Field`'s controls are
|
|
287
|
+
labeled individually (`aria-label`, `aria-labelledby`, or a `<label htmlFor>` matching
|
|
288
|
+
each control's `id`).
|
|
273
289
|
- [ ] Controlled inputs have both `value` and `onChange` (or use `defaultValue` uncontrolled).
|
|
274
290
|
- [ ] Error state shows via `color="danger"` + `message` + `messageColor="danger"`.
|
|
275
291
|
- [ ] Grouped/addon layouts use explicit `Field` + `Control` composition.
|
|
@@ -8,16 +8,18 @@ via `useBulmaClasses`.
|
|
|
8
8
|
|
|
9
9
|
Container and layout. Compound parts: `Field.Label`, `Field.Body`.
|
|
10
10
|
|
|
11
|
-
| Prop | Type | Notes
|
|
12
|
-
| ----------------------- | ------------------------------------------------- |
|
|
13
|
-
| `horizontal` | `boolean` | Label + control side by side. Auto-wraps children in `Field.Body`.
|
|
14
|
-
| `grouped` | `boolean \| 'centered' \| 'right' \| 'multiline'` | Controls in a row.
|
|
15
|
-
| `hasAddons` | `boolean \| 'centered' \| 'right'` | Attached controls.
|
|
16
|
-
| `narrow` | `boolean` | Constrain to content width (inside horizontal bodies).
|
|
17
|
-
| `label` | `ReactNode` | Names the one control it holds: a composed base or convenience input
|
|
18
|
-
| `labelSize` | `'small' \| 'normal' \| 'medium' \| 'large'` | Label size.
|
|
19
|
-
| `labelProps` | label attributes | Props for the `<label>`. An explicit `htmlFor` (even `undefined`) takes over the association; otherwise it is set automatically.
|
|
20
|
-
| `textColor` / `bgColor` | Bulma color | Helper colors for the field.
|
|
11
|
+
| Prop | Type | Notes |
|
|
12
|
+
| ----------------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| `horizontal` | `boolean` | Label + control side by side. Auto-wraps children in `Field.Body`. |
|
|
14
|
+
| `grouped` | `boolean \| 'centered' \| 'right' \| 'multiline'` | Controls in a row. |
|
|
15
|
+
| `hasAddons` | `boolean \| 'centered' \| 'right'` | Attached controls. |
|
|
16
|
+
| `narrow` | `boolean` | Constrain to content width (inside horizontal bodies). |
|
|
17
|
+
| `label` | `ReactNode` | Names the one control it holds: a composed base or convenience input with no `id` of its own through a generated id, a group through `aria-labelledby` (skipped for `grouped`/`hasAddons`; a nested `Field` starts its own scope). |
|
|
18
|
+
| `labelSize` | `'small' \| 'normal' \| 'medium' \| 'large'` | Label size. |
|
|
19
|
+
| `labelProps` | label attributes | Props for the `<label>`. An explicit `htmlFor` (even `undefined`) takes over the association; otherwise it is set automatically. For its `id`, see below. |
|
|
20
|
+
| `textColor` / `bgColor` | Bulma color | Helper colors for the field. |
|
|
21
|
+
|
|
22
|
+
An `id` in `labelProps` is what a group's, a range `Slider`'s and an `Autocomplete` or `Taginput` suggestion list's `aria-labelledby` points at. A label wired by hand with `htmlFor` gets no generated id, so give it one, `labelProps={{ htmlFor, id }}`, for a range `Slider`, an `Autocomplete` or a `Taginput`. No group points at a label wired by hand.
|
|
21
23
|
|
|
22
24
|
## Control — `form/Control.tsx`
|
|
23
25
|
|
|
@@ -68,6 +70,9 @@ Field/Control/message props. `TextArea` adds `rows` and `hasFixedSize`.
|
|
|
68
70
|
|
|
69
71
|
- `Checkbox` / `Radio` — single controls with `color`, `size`, `checked`/`defaultChecked`,
|
|
70
72
|
`onChange`, `disabled`.
|
|
73
|
+
- `Checkbox` also takes `indeterminate`, the mixed state of a "Select all" box over a partly
|
|
74
|
+
checked list. It changes the look and the announcement, not the value: `checked` stays the
|
|
75
|
+
value, so set both from the list.
|
|
71
76
|
- `Checkboxes` — group wrapper managing an **array** value.
|
|
72
77
|
- `Radios` — group wrapper managing a **single** selected value.
|
|
73
78
|
|
|
@@ -131,7 +131,7 @@ Notes:
|
|
|
131
131
|
// Search box: input + button attached.
|
|
132
132
|
<Field hasAddons>
|
|
133
133
|
<Control isExpanded>
|
|
134
|
-
<InputBase placeholder="Search" />
|
|
134
|
+
<InputBase placeholder="Search" aria-label="Search" />
|
|
135
135
|
</Control>
|
|
136
136
|
<Control>
|
|
137
137
|
<Button color="primary">Search</Button>
|
|
@@ -141,14 +141,18 @@ Notes:
|
|
|
141
141
|
// Two controls on one row.
|
|
142
142
|
<Field grouped>
|
|
143
143
|
<Control>
|
|
144
|
-
<InputBase placeholder="First" />
|
|
144
|
+
<InputBase placeholder="First" aria-label="First name" />
|
|
145
145
|
</Control>
|
|
146
146
|
<Control>
|
|
147
|
-
<InputBase placeholder="Last" />
|
|
147
|
+
<InputBase placeholder="Last" aria-label="Last name" />
|
|
148
148
|
</Control>
|
|
149
149
|
</Field>
|
|
150
150
|
```
|
|
151
151
|
|
|
152
|
+
A `grouped` or `hasAddons` Field names none of the controls it holds, and a placeholder
|
|
153
|
+
disappears once the user types, so each input above carries an `aria-label`. For a visible
|
|
154
|
+
label instead, see the last item below.
|
|
155
|
+
|
|
152
156
|
Recipes built on addons, each written out in full in the docs:
|
|
153
157
|
|
|
154
158
|
- **Password reveal** ([docs](https://bestax.io/docs/api/form/input#password-reveal)): the
|
|
@@ -164,9 +168,10 @@ Recipes built on addons, each written out in full in the docs:
|
|
|
164
168
|
- **Split button** ([docs](https://bestax.io/docs/api/components/dropdown#split-button)): a
|
|
165
169
|
`Button` and a `Dropdown` in `Field hasAddons`, one `Control` each. `Buttons hasAddons` does
|
|
166
170
|
not join the Dropdown's trigger. Keep the default color, since the trigger takes no `color`.
|
|
167
|
-
- To label an addon row, put the label on an outer `Field` with `labelProps={{ htmlFor }}`
|
|
168
|
-
matching `id` on the input.
|
|
169
|
-
|
|
171
|
+
- To label an addon row, put the label on an outer `Field` with `labelProps={{ htmlFor, id }}`
|
|
172
|
+
and a matching `id` on the input. The label's `id` names an `Autocomplete`'s or `Taginput`'s
|
|
173
|
+
suggestion list or a range `Slider`'s thumbs, which point `aria-labelledby` at it. A label
|
|
174
|
+
inside the `hasAddons` field sits in the row beside the controls.
|
|
170
175
|
|
|
171
176
|
## Horizontal field with explicit label/body
|
|
172
177
|
|
|
@@ -46,6 +46,12 @@ or `library` silently renders nothing.
|
|
|
46
46
|
first thing to check. A redundant `fa-`/`mdi-` prefix in `name` is tolerated (stripped), but
|
|
47
47
|
don't rely on it.
|
|
48
48
|
|
|
49
|
+
**Material Symbols' font is several megabytes however few icons the app shows.** Keep the
|
|
50
|
+
default `material-symbols/outlined.css` import. When the user cares about download size,
|
|
51
|
+
offer a smaller font as an optimisation (a Google Fonts `icon_names` subset, a self-hosted
|
|
52
|
+
subset, or a single-weight package): `references/icon-libraries.md` has each one and its
|
|
53
|
+
trade-offs.
|
|
54
|
+
|
|
49
55
|
## Styling
|
|
50
56
|
|
|
51
57
|
- `size` — `'small' | 'medium' | 'large'` sizes the Bulma **container** (`is-small` ≈ 1rem,
|
|
@@ -105,6 +111,7 @@ everything else takes the standard `aria-label` attribute, e.g.
|
|
|
105
111
|
## References
|
|
106
112
|
|
|
107
113
|
- `references/icon-libraries.md` — per-library setup (install/import/CDN), the full
|
|
108
|
-
name-format and variant tables, `features` values,
|
|
114
|
+
name-format and variant tables, `features` values, making the Material Symbols font
|
|
115
|
+
smaller, and the blank-icon troubleshooting list.
|
|
109
116
|
- `examples/icon-usage.tsx` — runnable example: ConfigProvider setup, sizes, variants,
|
|
110
117
|
colors, IconText, and decorative-vs-labeled patterns.
|
|
@@ -44,7 +44,7 @@ Set it once on `ConfigProvider` and omit `library` everywhere else.
|
|
|
44
44
|
|
|
45
45
|
## Ionicons — `'ion'` ⚠️ value is `ion`, not `ionicons`
|
|
46
46
|
|
|
47
|
-
- **Setup:** a CDN script
|
|
47
|
+
- **Setup:** a CDN script in `index.html` (no npm package — it registers the
|
|
48
48
|
`<ion-icon>` web component):
|
|
49
49
|
|
|
50
50
|
```html
|
|
@@ -89,6 +89,39 @@ Set it once on `ConfigProvider` and omit `library` everywhere else.
|
|
|
89
89
|
Material Icons' `round`.
|
|
90
90
|
- **Features:** Bulma classes like `'is-size-1'`.
|
|
91
91
|
|
|
92
|
+
### Making the font smaller
|
|
93
|
+
|
|
94
|
+
`outlined.css` loads the whole variable font (every symbol at every fill, weight, grade and
|
|
95
|
+
optical size), several megabytes however few icons the app shows. Keep that import as the
|
|
96
|
+
default: it is one line, works offline, and any name renders. Offer a smaller font only when
|
|
97
|
+
the user cares about download size (a size budget, a Lighthouse flag, slow networks), and
|
|
98
|
+
present it as an optimisation with the trade-offs below:
|
|
99
|
+
|
|
100
|
+
- **One stylesheet per style rendered.** If the app has the bare `import 'material-symbols';`,
|
|
101
|
+
replace it with `outlined.css`, adding `rounded.css` or `sharp.css` only where a `variant`
|
|
102
|
+
uses them. The bare import ships every style's font in the build (a browser fetches each one
|
|
103
|
+
only when a page renders it). This is where the scaffold already stands, and the style left
|
|
104
|
+
is still the whole variable font, which the options below shrink.
|
|
105
|
+
- **Google Fonts subset.** Replace the import with a stylesheet link in `index.html`:
|
|
106
|
+
`<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined&icon_names=home,palette,settings&display=block" />`.
|
|
107
|
+
`icon_names` takes the ligature names comma-separated and sorted alphabetically, and keep
|
|
108
|
+
`&display=block` (it stops the names flashing as text). The returned CSS defines the same
|
|
109
|
+
`material-symbols-outlined` class, so components don't change. Costs: the browser fetches
|
|
110
|
+
the icons from Google (privacy, no offline use), and the list must hold every name the app
|
|
111
|
+
renders, since a missing one shows as plain text. `variant="rounded"`/`"sharp"` need
|
|
112
|
+
`Material+Symbols+Rounded`/`Material+Symbols+Sharp` requested too, and an app that sets
|
|
113
|
+
`font-variation-settings` must request those axes in the URL.
|
|
114
|
+
- **Self-hosted subset.** Cut a subset from the package's `.woff2` with a font subsetting tool
|
|
115
|
+
(fontTools' subsetter, for one), keeping the glyphs for the names the app renders, and load
|
|
116
|
+
it with the app's own `@font-face` plus the `.material-symbols-outlined` rules. No request to
|
|
117
|
+
Google, same list upkeep.
|
|
118
|
+
- **Fewer variations.** `@material-symbols/font-400`, from the same project, keeps every
|
|
119
|
+
symbol with weight, grade and optical size fixed, at a fraction of the size. Import
|
|
120
|
+
`@material-symbols/font-400/outlined.css` in place of `material-symbols/outlined.css`; it
|
|
121
|
+
defines the same class, and there is no list to maintain.
|
|
122
|
+
|
|
123
|
+
Docs: https://bestax.io/docs/guides/getting-started/alternative-icons#making-the-font-smaller
|
|
124
|
+
|
|
92
125
|
## One glyph, five names
|
|
93
126
|
|
|
94
127
|
| Glyph | fa | mdi | ion | material-icons / material-symbols |
|
|
@@ -105,7 +138,7 @@ Set it once on `ConfigProvider` and omit `library` everywhere else.
|
|
|
105
138
|
2. **Name format for that library** — kebab vs snake_case (see the table above); for
|
|
106
139
|
material-* a wrong name renders as literal text instead of a glyph.
|
|
107
140
|
3. **The library's CSS/script is actually loaded** — the import in `main.tsx` (or the
|
|
108
|
-
Ionicons
|
|
141
|
+
Ionicons script in `index.html`) must exist; bestax ships none of them.
|
|
109
142
|
4. **Variant availability** — Font Awesome free has no `light`/`duotone`/`thin`; brand
|
|
110
143
|
glyphs need `variant="brands"`.
|
|
111
144
|
|
|
@@ -59,13 +59,59 @@ Centered; a collection of items → Card grid. For mixed requests, pick the domi
|
|
|
59
59
|
`aria-controls`/`aria-labelledby` links come built in, so add no `onKeyDown` or ARIA
|
|
60
60
|
of your own; `onChange` fires when a tab is activated, never on
|
|
61
61
|
arrow-key focus), `Dropdown active/onActiveChange`,
|
|
62
|
+
`ToggleButton pressed/defaultPressed onPressedChange` for a button that stays on or off (not
|
|
63
|
+
`Button isActive`, which sets no `aria-pressed`; the label stays the same in both states; a
|
|
64
|
+
set goes in `Buttons hasAddons role="group"` with an `aria-label`, where it's also
|
|
65
|
+
`Buttons.ToggleButton`, and a row where only one can be on is `SegmentedControl`),
|
|
66
|
+
`SegmentedControl aria-label="…" value/defaultValue onChange` for a row of buttons where
|
|
67
|
+
only one can be on, such as a list or grid view (one `SegmentedControl.Segment value="…"` per
|
|
68
|
+
option; a radio group with the single tab stop and the arrow keys built in, so add no
|
|
69
|
+
`onKeyDown` of your own; an icon-only segment needs its own `aria-label`; the arrows check as
|
|
70
|
+
they move, so `onChange(value)` fires on each, and it can't be cleared; it carries no form
|
|
71
|
+
value, so a choice a form submits is `Radios`),
|
|
62
72
|
`Popover trigger={<Button>…</Button>} open/defaultOpen onOpenChange` (`trigger` is one
|
|
63
73
|
element, not a node: Popover clones it to add the click and ARIA, so never wrap it in a
|
|
64
74
|
clickable of your own; build the panel from `Popover.Header`/`Body`/`Footer`/`Close`, and a
|
|
65
75
|
panel without a `Popover.Header` needs `ariaLabel`),
|
|
76
|
+
`HoverCard trigger={<Link href="…">…</Link>}` for a preview on hover or keyboard focus
|
|
77
|
+
(`trigger` is one link or button, cloned the same way; the card's content goes straight in as
|
|
78
|
+
children with no sub-components; a trigger inside a `Paragraph` needs `appendToBody`; hover
|
|
79
|
+
and focus only unless `openOnTap` is set, which suits only a trigger with no tap action of its
|
|
80
|
+
own, such as a `Button` with no `onClick`; on touch a link trigger just follows the link, so
|
|
81
|
+
what the card shows must be on that page too, and a form is a `Popover`),
|
|
82
|
+
`ContextMenu aria-label="…" trigger={<Table.Tr>…</Table.Tr>}` for actions on a right-click
|
|
83
|
+
or a long press (`trigger` is one element, a row or a card, cloned rather than wrapped; the
|
|
84
|
+
items are `ContextMenu.Item`/`ContextMenu.Divider`, which are `Dropdown`'s; no `open` prop,
|
|
85
|
+
`onOpenChange` reports it; always put the same items in a visible `Dropdown` on the row too,
|
|
86
|
+
since a context menu is only a shortcut),
|
|
66
87
|
`Steps value={i}/onStepClick items={[{label, icon?}]}` (child form is `Steps.Step`, not
|
|
67
88
|
`Steps.Item`). `Reveal cascade` staggers only its **direct children** — to stagger a grid,
|
|
68
89
|
put `<Reveal delay={i * 80}>` inside each `Cell`, not around the container.
|
|
90
|
+
- **An empty list, table or search with no results is `EmptyState`**, not a hand-built `Box`
|
|
91
|
+
of `Icon`, `Title` and `Buttons isCentered`:
|
|
92
|
+
`<EmptyState icon="folder-open" title="No projects yet" titleAs="h3" description="…">` with
|
|
93
|
+
its one `<Button>` as a child (two or more go in `EmptyState.Actions`). Its `title` is always a
|
|
94
|
+
heading, so set `titleAs` to the level under the list's own heading. It has no live region:
|
|
95
|
+
after a search or a filter, keep a `Paragraph` with `role="status"`, `aria-atomic="true"` and
|
|
96
|
+
`visibility="sr-only"` beside the search field the whole time, saying the count, and have
|
|
97
|
+
"Clear search" put focus back in the field. While data loads show a `Skeleton` or a `Loader`,
|
|
98
|
+
and a failed load is a `Message` or a `Notification`, never an EmptyState. The archetypes
|
|
99
|
+
reference has the full pattern under Card grid.
|
|
100
|
+
- **An order's history, a ticket's activity, a changelog or an activity feed is `Timeline`**,
|
|
101
|
+
not a hand-built list with a border down the side: `<Timeline titleAs="h3">` with a
|
|
102
|
+
`<Timeline.Item title="Shipped" time="14 October 2026" dateTime="2026-10-14">` per event, any
|
|
103
|
+
detail as its children, and the whole date in `time`. `titleAs` is the level under the
|
|
104
|
+
section's heading, or `"p"` in a busy feed. For something that moves along on its own, like a
|
|
105
|
+
parcel or a deployment, `active={i}` marks progress and `pending="Out for delivery"` ends it on
|
|
106
|
+
what's under way, oldest first. A journey the user moves through, like a wizard or a checkout,
|
|
107
|
+
stays `Steps`.
|
|
108
|
+
- **A file browser, a checkbox tree (permissions, filters) or an outline you browse is
|
|
109
|
+
`TreeView`**, with `<TreeView.Item value="…" label="…" icon="folder">` items nested inside each
|
|
110
|
+
other: `<TreeView aria-label="Files" selectionMode="single">` with `selected`/`onSelectedChange`
|
|
111
|
+
picks one, and `selectionMode="checkbox"` with `checked`/`onCheckedChange` draws boxes with
|
|
112
|
+
mixed parents, where a parent's value checks its whole branch. Map the data with a recursive
|
|
113
|
+
function, and render an item's children whether it's open or not. Its items aren't links, so
|
|
114
|
+
the app's sidebar and any list of pages stay on `Menu` with `Menu.Item href`.
|
|
69
115
|
- Link lists (footer nav, sidebars): a bare `UnorderedList` of `ListItem`s is already
|
|
70
116
|
marker-less and flush — Bulma's reset unstyles `ul` — so no prop or CSS is needed;
|
|
71
117
|
bullets appear only inside `Content`.
|
|
@@ -160,7 +206,7 @@ background comes from the semantic `color` prop, not `bgColor`.
|
|
|
160
206
|
| `textAlign: 'center'` | `textAlign="centered"` (also `left`, `right`, `justified`) |
|
|
161
207
|
| `color: '#…'` | `textColor` with the nearest Bulma color: `primary`, `link`, `info`, `success`, `warning`, `danger`, `white`, `black`, `grey` (+ `grey-light`, `grey-dark`, …) |
|
|
162
208
|
| `backgroundColor: '#…'` | `bgColor` (same palette) |
|
|
163
|
-
| `fontSize: …` | `textSize="1"`…`"7"` (`1` largest)
|
|
209
|
+
| `fontSize: …` | `textSize="1"`…`"7"` (`1` largest). On `Title`/`SubTitle`, `as` sets the level and `size` picks it too, so an `<h3>` at size 4 is `as="h3" textSize="4"` |
|
|
164
210
|
| `fontWeight: …` | `textWeight`: `light`, `normal`, `medium`, `semibold`, `bold` |
|
|
165
211
|
| `textTransform`, italics | `textTransform`: `uppercase`, `lowercase`, `capitalized`, `italic` |
|
|
166
212
|
| `display: 'flex'` + flex properties | same-named props: `display="flex"`, `flexDirection`, `justifyContent`, `alignItems`, `flexWrap` |
|
|
@@ -168,9 +214,11 @@ background comes from the semantic `color` prop, not `bgColor`.
|
|
|
168
214
|
| `display: 'none'` | `visibility="hidden"`, or responsive `display*` props (`displayMobile`, `displayTablet`, …) |
|
|
169
215
|
| `gap: '1rem'` (flex or grid) | `gap="2"`, or one axis: `columnGap`, `rowGap`; gap scale: `1`=0.5rem, `2`=1rem, `4`=2rem, half steps (`"1.5"`) |
|
|
170
216
|
|
|
171
|
-
No helper matches (e.g. `maxWidth`, a one-off gradient)? Add a named class to the project
|
|
172
|
-
stylesheet
|
|
173
|
-
|
|
217
|
+
No helper matches (e.g. `maxWidth`, a one-off gradient)? Add a named class to the project's
|
|
218
|
+
global stylesheet and pass it via `className` — still never inline `style`. In a create-bestax
|
|
219
|
+
app that is `src/index.css`, which its entry file imports after bestax's stylesheet, so the
|
|
220
|
+
class wins over a Bulma rule of the same weight. The starter page's `src/App.css` loads after
|
|
221
|
+
bestax's too, but only while the starter's `App` imports it.
|
|
174
222
|
|
|
175
223
|
## References
|
|
176
224
|
|
|
@@ -200,6 +248,8 @@ inline `style`.
|
|
|
200
248
|
- [ ] Do not use `Tile` — it is not shipped.
|
|
201
249
|
- [ ] Action feedback goes through `Toast`, a confirmation through `Dialog`, a text-styled
|
|
202
250
|
action through `LinkButton` — not `Notification`, `Modal` or a bare `<a>`.
|
|
251
|
+
- [ ] An empty list or a search with no results renders `EmptyState`, and a search or a filter
|
|
252
|
+
keeps a `role="status"` region beside its field that says the count.
|
|
203
253
|
- [ ] Style with helper props, not inline `style` — translate via the mapping table; values
|
|
204
254
|
with no helper get a named class in the stylesheet, never `style={{}}`. No raw Bulma
|
|
205
255
|
`className`s either (`Span`/`Paragraph` wrap bare text; `Th`/`Td` take `textAlign`/`textWeight`).
|