create-bestax 4.2.18 → 4.2.19
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/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +4 -1
- package/dist/file-system.d.ts +5 -1
- package/dist/file-system.d.ts.map +1 -1
- package/dist/file-system.js +11 -1
- package/dist/library-range.d.ts +16 -0
- package/dist/library-range.d.ts.map +1 -0
- package/dist/library-range.js +40 -0
- package/dist/project-creator.d.ts.map +1 -1
- package/dist/project-creator.js +4 -1
- package/package.json +7 -7
- package/templates/skills/bestax-custom-component/references/component-catalog.md +3 -1
- package/templates/skills/bestax-form/SKILL.md +40 -15
- package/templates/skills/bestax-form/references/api.md +10 -8
- package/templates/skills/bestax-form/references/patterns.md +68 -0
- package/templates/skills/bestax-layout-scaffold/SKILL.md +16 -5
- package/templates/skills/bestax-layout-scaffold/references/archetypes.md +2 -2
- package/templates/skills/bestax-layout-scaffold/references/layout-components.md +52 -5
- package/templates/skills/bestax-migrate/SKILL.md +11 -3
- package/templates/skills/bestax-migrate/references/bloomer/component-map.md +1 -1
- package/templates/skills/bestax-migrate/references/bloomer/prop-map.md +8 -5
- package/templates/skills/bestax-migrate/references/bulma-classes/component-map.md +144 -101
- package/templates/skills/bestax-migrate/references/bulma-classes/prop-map.md +47 -16
- package/templates/skills/bestax-migrate/references/bulma-classes/unmappables.md +217 -45
- package/templates/skills/bestax-migrate/references/css-migration.md +12 -5
- package/templates/skills/bestax-migrate/references/rbx/prop-map.md +19 -13
- package/templates/skills/bestax-migrate/references/react-bulma-components/prop-map.md +27 -22
- package/templates/skills/bestax-migrate/references/react-bulma-components/unmappables.md +1 -1
- package/templates/skills/bestax-optimize/references/modular-build.md +57 -17
- package/templates/skills/bestax-theming/SKILL.md +19 -8
- package/templates/skills/bestax-theming/examples/dark-mode.tsx +99 -17
- package/templates/skills/bestax-theming/examples/theme-config.tsx +13 -8
- package/templates/skills/bestax-theming/references/css-variables.md +188 -18
- package/templates/skills/bestax-theming/references/themeable-components.md +2 -0
- package/templates/vite/package.json +1 -1
- package/templates/vite/src/main.jsx +1 -1
- package/templates/vite-ts/package.json +1 -1
- package/templates/vite-ts/src/main.tsx +1 -1
|
@@ -6,31 +6,36 @@ this automatically for literal values; dynamic values get TODOs.
|
|
|
6
6
|
|
|
7
7
|
## Renames and value changes
|
|
8
8
|
|
|
9
|
-
| RBC | bestax
|
|
10
|
-
| --------------------------------- |
|
|
11
|
-
| `renderAs` | `as` — **only**
|
|
12
|
-
| `domRef` | flagged — rename to `ref` on the components that forward one, else use a DOM child
|
|
13
|
-
| `backgroundColor` | `bgColor`
|
|
14
|
-
| `textColor` | `textColor` (same)
|
|
15
|
-
| `colorVariant` | flagged — use `isLight` or a color shade
|
|
16
|
-
| `textSize={4}` | `textSize="4"` (string union `'1'`–`'7'`)
|
|
17
|
-
| `textAlign="center"` | `textAlign="centered"` (`justify`→`justified`)
|
|
18
|
-
| `textFamily` | `fontFamily`
|
|
19
|
-
| `italic` | `textTransform="italic"`
|
|
20
|
-
| `m/mt/…/py={3}` | `m="3"` (string union `'0'`–`'6'`)
|
|
21
|
-
| `pull="right"` | `float="right"`
|
|
22
|
-
| `marginless` / `paddingless` | `m="0"` / `p="0"`
|
|
23
|
-
| `clipped` | `overflow="clipped"`
|
|
24
|
-
| `radiusless` / `shadowless` | `radius="radiusless"` / `shadow="shadowless"`
|
|
25
|
-
| `unselectable` | `interaction="unselectable"`
|
|
26
|
-
| `hidden` / `invisible` / `srOnly` | `visibility="hidden"` / `"invisible"` / `"sr-only"`
|
|
27
|
-
| `display="hidden"` | `visibility="hidden"`
|
|
28
|
-
| `display="relative"` | boolean `relative`
|
|
29
|
-
| flexbox props | same names/values (`flexDirection`, `justifyContent`, …)
|
|
30
|
-
| `clearfix`, `overlay` | same
|
|
9
|
+
| RBC | bestax |
|
|
10
|
+
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| `renderAs` | `as` — **only** where bestax declares one ([listed below](#renderas--as-and-the-props-that-follow-it)); elsewhere flagged |
|
|
12
|
+
| `domRef` | flagged — rename to `ref` on the components that forward one, else use a DOM child |
|
|
13
|
+
| `backgroundColor` | `bgColor` |
|
|
14
|
+
| `textColor` | `textColor` (same) |
|
|
15
|
+
| `colorVariant` | flagged — use `isLight` or a color shade |
|
|
16
|
+
| `textSize={4}` | `textSize="4"` (string union `'1'`–`'7'`) |
|
|
17
|
+
| `textAlign="center"` | `textAlign="centered"` (`justify`→`justified`) |
|
|
18
|
+
| `textFamily` | `fontFamily` |
|
|
19
|
+
| `italic` | `textTransform="italic"` |
|
|
20
|
+
| `m/mt/…/py={3}` | `m="3"` (string union `'0'`–`'6'`) |
|
|
21
|
+
| `pull="right"` | `float="right"` |
|
|
22
|
+
| `marginless` / `paddingless` | `m="0"` / `p="0"` |
|
|
23
|
+
| `clipped` | `overflow="clipped"` |
|
|
24
|
+
| `radiusless` / `shadowless` | `radius="radiusless"` / `shadow="shadowless"` |
|
|
25
|
+
| `unselectable` | `interaction="unselectable"` |
|
|
26
|
+
| `hidden` / `invisible` / `srOnly` | `visibility="hidden"` / `"invisible"` / `"sr-only"` |
|
|
27
|
+
| `display="hidden"` | `visibility="hidden"` |
|
|
28
|
+
| `display="relative"` | boolean `relative` |
|
|
29
|
+
| flexbox props | same names/values (`flexDirection`, `justifyContent`, …) |
|
|
30
|
+
| `clearfix`, `overlay` | same |
|
|
31
31
|
|
|
32
32
|
## `renderAs` → `as`, and the props that follow it
|
|
33
33
|
|
|
34
|
+
`renderAs` becomes `as` on the components bestax declares one on: `Button`, `Title` and
|
|
35
|
+
`SubTitle` (from `Heading`), `Image`, `Form.Control`, `Footer`, `Level.Item`, `Media` and its
|
|
36
|
+
`Item`s, `Menu.Item`, `Navbar.Item`, `Navbar.Link`, `Dropdown.Item`, `Container`, `Hero`,
|
|
37
|
+
`Hero.Header`, `Hero.Footer`, `Panel` and `Panel.Block`. Everywhere else it is flagged.
|
|
38
|
+
|
|
34
39
|
bestax gives an element the attributes of the tag `as` names, and several components narrow
|
|
35
40
|
`as` to the tags Bulma's markup allows there (`Footer` is `'footer' | 'div'`, `Control` is
|
|
36
41
|
`'div' | 'p'`, `Level.Item` is `'div' | 'p' | 'a'`). RBC's `renderAs` took any tag, so two
|
|
@@ -132,7 +132,7 @@ bestax `Button` colors are the semantic set + `text`/`ghost`. For shades use
|
|
|
132
132
|
## `colorVariant` / Hero `gradient` / Hero `halfheight` (`prop:size`)
|
|
133
133
|
|
|
134
134
|
- `colorVariant="light"` → `isLight` where supported (Button, Notification), otherwise a
|
|
135
|
-
shade: `bgColor="primary
|
|
135
|
+
shade: `bgColor="primary" backgroundColorShade="90"`.
|
|
136
136
|
- Bulma v1 removed `is-bold` hero gradients — delete `gradient` or restyle with CSS.
|
|
137
137
|
- `halfheight` has no bestax size; use `size="medium"` or a CSS height.
|
|
138
138
|
|
|
@@ -49,6 +49,11 @@ extras map to `@allxsmith/bestax-bulma/scss/<category>/<lowercased-name>`. Compo
|
|
|
49
49
|
are pure stock Bulma (Button, Box, Card, Navbar, Modal, Hero, …) have **no** extras partial —
|
|
50
50
|
Bulma's own module is all they need.
|
|
51
51
|
|
|
52
|
+
Bulma groups its form controls, so their modules don't follow the name: `Input` and `TextArea`
|
|
53
|
+
are `bulma/sass/form/input-textarea`, `Checkbox`, `Checkboxes`, `Radio` and `Radios` are
|
|
54
|
+
`bulma/sass/form/checkbox-radio`, and `Field` and `Control` are `bulma/sass/form/tools`.
|
|
55
|
+
`Select` and `File` follow the rule.
|
|
56
|
+
|
|
52
57
|
Stock Bulma examples:
|
|
53
58
|
|
|
54
59
|
```scss
|
|
@@ -56,8 +61,9 @@ Stock Bulma examples:
|
|
|
56
61
|
@use 'bulma/sass/elements/box'; // Box
|
|
57
62
|
@use 'bulma/sass/elements/notification'; // Notification
|
|
58
63
|
@use 'bulma/sass/elements/title'; // Title, SubTitle
|
|
59
|
-
@use 'bulma/sass/form/input'; // Input
|
|
64
|
+
@use 'bulma/sass/form/input-textarea'; // Input, TextArea
|
|
60
65
|
@use 'bulma/sass/form/select'; // Select
|
|
66
|
+
@use 'bulma/sass/form/tools'; // Field, Control
|
|
61
67
|
@use 'bulma/sass/components/card'; // Card
|
|
62
68
|
@use 'bulma/sass/components/modal'; // Modal
|
|
63
69
|
@use 'bulma/sass/components/navbar'; // Navbar
|
|
@@ -74,12 +80,13 @@ is **more complete** than the docs page's Option C example, which omits several)
|
|
|
74
80
|
|
|
75
81
|
**Components** (`@allxsmith/bestax-bulma/scss/components/<name>`):
|
|
76
82
|
`loading`, `collapse`, `tooltip`, `popover`, `hovercard`, `contextmenu`, `steps`, `sidebar`,
|
|
77
|
-
`toast`, `dialog`, `carousel`, `tabs`, `reveal`, `avatar`, `avatars`, `badge`,
|
|
78
|
-
`timeline`, `treeview`
|
|
83
|
+
`toast`, `dialog`, `carousel`, `tabs`, `menu`, `reveal`, `avatar`, `avatars`, `badge`,
|
|
84
|
+
`emptystate`, `timeline`, `treeview`
|
|
79
85
|
|
|
80
86
|
**Form** (`@allxsmith/bestax-bulma/scss/form/<name>`):
|
|
81
|
-
`checkbox`, `radio`, `switch`, `slider`, `numberinput`, `
|
|
82
|
-
`picker-popover`, `dateinput`, `timeinput`, `datetimeinput`,
|
|
87
|
+
`checkbox`, `radio`, `switch`, `slider`, `numberinput`, `pininput`, `rate`, `autocomplete`,
|
|
88
|
+
`combobox`, `taginput`, `picker-popover`, `dateinput`, `timeinput`, `datetimeinput`,
|
|
89
|
+
`daterangeinput`, `file`
|
|
83
90
|
|
|
84
91
|
**Elements** (`@allxsmith/bestax-bulma/scss/elements/<name>`): `kbd`, `linkbutton`, `loader`,
|
|
85
92
|
`notification`, `progress`, `skeleton`
|
|
@@ -90,29 +97,62 @@ Notes:
|
|
|
90
97
|
|
|
91
98
|
- The extras partial name is the component name lowercased (`Dialog` →
|
|
92
99
|
`scss/components/dialog`; `NumberInput` → `scss/form/numberinput`).
|
|
93
|
-
-
|
|
94
|
-
|
|
100
|
+
- Sass names a `@use` after the last part of its URL and refuses two with the same name in one
|
|
101
|
+
file. A partial that extends a stock component often shares that name with the component's
|
|
102
|
+
Bulma module (`components/tabs`, `elements/notification`), so give the bestax line a name of
|
|
103
|
+
its own with `as`, the way the notes below do. The name changes nothing in the compiled CSS.
|
|
104
|
+
- Extras `Tabs` **extends** stock Bulma tabs (vertical variant), so an app using `Tabs` needs
|
|
105
|
+
both:
|
|
106
|
+
|
|
107
|
+
```scss
|
|
108
|
+
@use 'bulma/sass/components/tabs';
|
|
109
|
+
@use '@allxsmith/bestax-bulma/scss/components/tabs' as bestax-tabs;
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
- The `menu` partial **extends** stock Bulma menu (it draws `Menu.Section`'s heading and chevron,
|
|
113
|
+
and keeps the active item marked in forced colors, Windows' contrast themes), so an app using
|
|
114
|
+
`Menu` needs both, in either order:
|
|
115
|
+
|
|
116
|
+
```scss
|
|
117
|
+
@use 'bulma/sass/components/menu';
|
|
118
|
+
@use '@allxsmith/bestax-bulma/scss/components/menu' as bestax-menu;
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
- The `file` partial **extends** stock Bulma file (File's keyboard focus ring, and the rounded
|
|
122
|
+
corners of a boxed `hasName` File with no name to show), so an app using `File` needs both, in
|
|
123
|
+
either order:
|
|
124
|
+
|
|
125
|
+
```scss
|
|
126
|
+
@use 'bulma/sass/form/file';
|
|
127
|
+
@use '@allxsmith/bestax-bulma/scss/form/file' as bestax-file;
|
|
128
|
+
```
|
|
129
|
+
|
|
95
130
|
- `DateInput`/`TimeInput`/`DateTimeInput` also need `picker-popover`.
|
|
96
131
|
- `DateRangeInput` needs `daterangeinput` for its field and `dateinput` for its calendar, and
|
|
97
132
|
`picker-popover` too.
|
|
133
|
+
- `PinInput`'s boxes are Bulma inputs, so an app using it needs `bulma/sass/form/input-textarea`
|
|
134
|
+
next to `scss/form/pininput`, and `bulma/sass/form/tools` for the label and help text around it.
|
|
98
135
|
- `Popover.Close` renders a Bulma button, so an app using it needs `bulma/sass/elements/button`
|
|
99
136
|
next to `scss/components/popover`.
|
|
100
137
|
- `ContextMenu`'s menu is Bulma's dropdown markup, so an app using it needs
|
|
101
138
|
`bulma/sass/components/dropdown` next to `scss/components/contextmenu`.
|
|
102
139
|
- `Loader` is stock Bulma styled by `bulma/sass/elements/loader`, and the `isLoading`
|
|
103
140
|
spinners on `Button`, `Control` and `Select` are Bulma's too. The `loader` extras partial
|
|
104
|
-
stops all of them under `prefers-reduced-motion: reduce
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
141
|
+
stops all of them under `prefers-reduced-motion: reduce`, hides everything inside a loading
|
|
142
|
+
button where Bulma hides only the label, and keeps each ring open in forced colors (Windows'
|
|
143
|
+
contrast themes), so include it (`as bestax-loader`) next to the Bulma modules in any build
|
|
144
|
+
that shows one. Its
|
|
145
|
+
rules win over Bulma's either way, so the order of the two `@use` lines does not matter.
|
|
108
146
|
- `Progress` and the skeletons are stock Bulma too (`bulma/sass/elements/progress`,
|
|
109
|
-
`bulma/sass/base/skeleton`). The `progress` and `skeleton` extras partials
|
|
110
|
-
indeterminate bar's sweep and the
|
|
111
|
-
|
|
147
|
+
`bulma/sass/base/skeleton`). The `progress` and `skeleton` extras partials
|
|
148
|
+
(`as bestax-progress`, `as bestax-skeleton`) stop the indeterminate bar's sweep and the
|
|
149
|
+
skeleton pulse (`Skeleton`, the `skeleton` prop, `hasSkeleton`) under
|
|
150
|
+
`prefers-reduced-motion: reduce`, the same way and in either order.
|
|
112
151
|
- `Notification` is stock Bulma too (`bulma/sass/elements/notification`). The `notification`
|
|
113
152
|
extras partial pads the end of one with a close button (`hasDelete`, or a `Delete` passed in)
|
|
114
|
-
so its text clears the button, so include it next to the Bulma
|
|
115
|
-
a dismissible notification. It doesn't depend on the order of
|
|
153
|
+
so its text clears the button, so include it (`as bestax-notification`) next to the Bulma
|
|
154
|
+
module in any build that shows a dismissible notification. It doesn't depend on the order of
|
|
155
|
+
the two `@use` lines either.
|
|
116
156
|
- Stock-Bulma form controls still need their Bulma module (`bulma/sass/form/…`); the extras
|
|
117
157
|
form partials above style only the bestax-specific behavior.
|
|
118
158
|
- Bulma component modules pull their own internal sub-elements — e.g.
|
|
@@ -194,7 +234,7 @@ Scaffolded with an icon library, the starter also needs `bulma/sass/elements/ico
|
|
|
194
234
|
@use 'bulma/sass/helpers/flexbox'; // flexGrow
|
|
195
235
|
@use 'bulma/sass/helpers/typography'; // textAlign
|
|
196
236
|
|
|
197
|
-
@use '@allxsmith/bestax-bulma/scss/elements/notification'; // text clears the close button
|
|
237
|
+
@use '@allxsmith/bestax-bulma/scss/elements/notification' as bestax-notification; // text clears the close button
|
|
198
238
|
```
|
|
199
239
|
|
|
200
240
|
```tsx
|
|
@@ -11,9 +11,14 @@ properties. Theme an app by overriding the right variables — no component re-s
|
|
|
11
11
|
|
|
12
12
|
## Approach
|
|
13
13
|
|
|
14
|
-
Recolor a brand color by overriding its **hue/saturation/lightness trio**
|
|
15
|
-
|
|
16
|
-
the
|
|
14
|
+
Recolor a brand color by overriding its **hue/saturation/lightness trio**
|
|
15
|
+
(`--bulma-<color>-h` / `-s` / `-l`). The color itself follows all three. Its shades and variants
|
|
16
|
+
follow the hue and saturation, at lightnesses the trio doesn't set. Among those, the stylesheet's
|
|
17
|
+
build picked the label on a solid color (`--bulma-<color>-invert-l`) and the color's text on the
|
|
18
|
+
page (`--bulma-<color>-on-scheme-l`, which for `link` is every link) for contrast with the color
|
|
19
|
+
it was built with, so when the new color reads lighter or darker than the built one, set them in
|
|
20
|
+
your CSS as well, or labels and text lose their contrast (`references/css-variables.md`,
|
|
21
|
+
"Colors").
|
|
17
22
|
|
|
18
23
|
Choose an override path:
|
|
19
24
|
|
|
@@ -31,7 +36,10 @@ For **dark mode**, pass `colorMode` to `Theme` (`'light' | 'dark' | 'system'`).
|
|
|
31
36
|
`data-theme` attribute on `<html>` (plus `data-<prefix>theme` inside a `ConfigProvider` with a
|
|
32
37
|
`classPrefix`, which the prefixed flavors read), flipping the light/dark scheme — global, even on a scoped
|
|
33
38
|
`Theme`; `'system'` follows the OS `prefers-color-scheme`. Drive it from state on the app-root
|
|
34
|
-
`Theme`: `<Theme isRoot colorMode={mode}>`.
|
|
39
|
+
`Theme`: `<Theme isRoot colorMode={mode}>`. `Theme` writes the attribute from an effect, after a
|
|
40
|
+
client-rendered app's first paint, so the scheme of that first frame comes from `index.html`:
|
|
41
|
+
`data-theme` on the `<html>` for a scheme that never changes, or an inline `<head>` script that
|
|
42
|
+
writes the visitor's saved choice for a toggle (`examples/dark-mode.tsx`).
|
|
35
43
|
|
|
36
44
|
## Contrast rules (dark mode is on by default)
|
|
37
45
|
|
|
@@ -42,8 +50,10 @@ theme. Custom fixed tokens (`--my-canvas: #f6f4ec`) do **not** flip — producin
|
|
|
42
50
|
text on the author's fixed light background. Apply exactly one of these rules whenever custom
|
|
43
51
|
color tokens or fixed-color surfaces exist:
|
|
44
52
|
|
|
45
|
-
- **Single-mode design → pin the scheme
|
|
46
|
-
an OS preference can never invert text
|
|
53
|
+
- **Single-mode design → pin the scheme on `<html>`.** `<html lang="en" data-theme="light">` (or
|
|
54
|
+
`"dark"`) in `index.html`, so the first paint has it and an OS preference can never invert text
|
|
55
|
+
out from under the fixed palette. `<Theme isRoot colorMode="light">` alone sets it only after
|
|
56
|
+
that paint, which shows the OS scheme first.
|
|
47
57
|
- **Both modes → no exposed fixed tokens.** Derive custom tokens from scheme variables
|
|
48
58
|
(`--my-canvas: var(--bulma-scheme-main)`) — or flip them yourself under **both** dark-mode
|
|
49
59
|
paths: `[data-theme='dark']` **and** `@media (prefers-color-scheme: dark)` scoped to
|
|
@@ -113,13 +123,14 @@ Nest `Theme` and `ConfigProvider` together at the root (order doesn't matter).
|
|
|
113
123
|
## Examples
|
|
114
124
|
|
|
115
125
|
- `examples/theme-config.tsx` — a custom brand theme at the app root, plus a scoped override.
|
|
116
|
-
- `examples/dark-mode.tsx` — a light/dark toggle
|
|
126
|
+
- `examples/dark-mode.tsx` — a light/dark toggle that keeps the visitor's choice, with the `index.html` script that writes Bulma's `data-theme` from it before the first paint.
|
|
117
127
|
|
|
118
128
|
## Checklist
|
|
119
129
|
|
|
120
|
-
- [ ] Recolor brand colors via the HSL trio (`*-h` / `*-s` / `*-l`), not by hard-coding hex on components.
|
|
130
|
+
- [ ] Recolor brand colors via the HSL trio (`*-h` / `*-s` / `*-l`), not by hard-coding hex on components; a color that reads lighter or darker than the built one also sets `*-invert-l` and `*-on-scheme-l` in CSS.
|
|
121
131
|
- [ ] Apply a global theme once with `<Theme isRoot>` (or `:root`); use scoped `<Theme>` for one-off sections.
|
|
122
132
|
- [ ] Set non-color tokens (radius, fonts, sizes) through `bulmaVars` or `:root`; a custom `--bulma-family-*` needs its font actually loaded (`index.html` `<link>` or an `@fontsource` import).
|
|
123
133
|
- [ ] Implement dark mode with `data-theme` on `<html>` (plus `data-<prefix>theme` inside a `ConfigProvider` with a `classPrefix`, which the prefixed flavors read); do not expect a shipped dark-mode component.
|
|
134
|
+
- [ ] Set the first paint's scheme in `index.html`: on `<html>` for a fixed scheme, from an inline `<head>` script for a saved choice.
|
|
124
135
|
- [ ] Pass `color`/`textColor`/`bgColor` (not custom CSS) to color individual components.
|
|
125
136
|
- [ ] Set the icon library once with `<ConfigProvider iconLibrary="…">` at the root, not `library` on every `<Icon>`.
|
|
@@ -1,38 +1,120 @@
|
|
|
1
|
-
//
|
|
1
|
+
// A light/dark toggle for an app built on @allxsmith/bestax-bulma that keeps
|
|
2
|
+
// the visitor's choice.
|
|
2
3
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
// Bulma reads the scheme from `data-theme` on <html>. `Theme colorMode` writes
|
|
5
|
+
// that attribute from an effect, which runs after a client-rendered app's
|
|
6
|
+
// first paint, so on its own the page shows the OS scheme for a frame and then
|
|
7
|
+
// flips. The scheme for that first frame comes from a script in index.html,
|
|
8
|
+
// which writes the saved choice, or else the OS preference, before anything
|
|
9
|
+
// renders. Put it inside <head> as a plain inline script, not
|
|
10
|
+
// `type="module"`, so it runs before the body renders:
|
|
11
|
+
//
|
|
12
|
+
// <script>
|
|
13
|
+
// // Bulma's scheme on <html> before the first paint: the saved choice,
|
|
14
|
+
// // or else the OS preference. The app saves under the same key.
|
|
15
|
+
// (() => {
|
|
16
|
+
// let saved = null;
|
|
17
|
+
// try {
|
|
18
|
+
// saved = localStorage.getItem('theme');
|
|
19
|
+
// } catch {
|
|
20
|
+
// // Storage is blocked, so fall back to the OS preference.
|
|
21
|
+
// }
|
|
22
|
+
// const dark =
|
|
23
|
+
// saved === 'light' || saved === 'dark'
|
|
24
|
+
// ? saved === 'dark'
|
|
25
|
+
// : window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
26
|
+
// document.documentElement.setAttribute(
|
|
27
|
+
// 'data-theme',
|
|
28
|
+
// dark ? 'dark' : 'light'
|
|
29
|
+
// );
|
|
30
|
+
// })();
|
|
31
|
+
// </script>
|
|
32
|
+
//
|
|
33
|
+
// This file is the app's side: it starts from the attribute the script wrote,
|
|
34
|
+
// passes its state to `Theme colorMode`, which keeps the attribute in step
|
|
35
|
+
// with the toggle, and saves each choice under the key the script reads.
|
|
36
|
+
// Under a ConfigProvider `classPrefix`, colorMode also writes
|
|
37
|
+
// `data-<prefix>theme`, which the prefixed flavors read, so the script writes
|
|
38
|
+
// that attribute too.
|
|
8
39
|
import { useState } from 'react';
|
|
9
40
|
import {
|
|
10
41
|
Theme,
|
|
11
42
|
Box,
|
|
12
|
-
Button,
|
|
13
43
|
Title,
|
|
14
44
|
Notification,
|
|
45
|
+
ToggleButton,
|
|
15
46
|
} from '@allxsmith/bestax-bulma';
|
|
16
47
|
|
|
17
|
-
type
|
|
48
|
+
export type ColorMode = 'light' | 'dark';
|
|
49
|
+
|
|
50
|
+
/** The key the index.html script reads the saved choice from. */
|
|
51
|
+
const STORAGE_KEY = 'theme';
|
|
52
|
+
|
|
53
|
+
const isColorMode = (value: unknown): value is ColorMode =>
|
|
54
|
+
value === 'light' || value === 'dark';
|
|
55
|
+
|
|
56
|
+
function savedColorMode(): ColorMode | null {
|
|
57
|
+
try {
|
|
58
|
+
const saved = localStorage.getItem(STORAGE_KEY);
|
|
59
|
+
return isColorMode(saved) ? saved : null;
|
|
60
|
+
} catch {
|
|
61
|
+
// A browser can block storage, in a private window or an embedded
|
|
62
|
+
// preview.
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The scheme the index.html script wrote. Without the script, the same
|
|
69
|
+
* choice: the saved one, or the OS preference.
|
|
70
|
+
*
|
|
71
|
+
* `useState` calls this during render, and a server render has no document,
|
|
72
|
+
* so there it is 'light' and the browser's own render starts from the
|
|
73
|
+
* attribute.
|
|
74
|
+
*/
|
|
75
|
+
export function initialColorMode(): ColorMode {
|
|
76
|
+
if (typeof document === 'undefined') return 'light';
|
|
77
|
+
const written = document.documentElement.getAttribute('data-theme');
|
|
78
|
+
if (isColorMode(written)) return written;
|
|
79
|
+
return (
|
|
80
|
+
savedColorMode() ??
|
|
81
|
+
(window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
82
|
+
? 'dark'
|
|
83
|
+
: 'light')
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Remember the visitor's choice for their next visit. */
|
|
88
|
+
export function saveColorMode(mode: ColorMode) {
|
|
89
|
+
try {
|
|
90
|
+
localStorage.setItem(STORAGE_KEY, mode);
|
|
91
|
+
} catch {
|
|
92
|
+
// Storage is blocked, so the choice lasts until the page reloads.
|
|
93
|
+
}
|
|
94
|
+
}
|
|
18
95
|
|
|
19
96
|
export function DarkModeToggle() {
|
|
20
|
-
const [mode, setMode] = useState<
|
|
97
|
+
const [mode, setMode] = useState<ColorMode>(initialColorMode);
|
|
98
|
+
|
|
99
|
+
const setDark = (dark: boolean) => {
|
|
100
|
+
const next = dark ? 'dark' : 'light';
|
|
101
|
+
setMode(next);
|
|
102
|
+
saveColorMode(next);
|
|
103
|
+
};
|
|
21
104
|
|
|
22
105
|
return (
|
|
106
|
+
// Wrap the app once, at its root.
|
|
23
107
|
<Theme isRoot colorMode={mode}>
|
|
24
108
|
<Box>
|
|
25
|
-
<Title size="5">Color
|
|
109
|
+
<Title size="5">Color scheme</Title>
|
|
26
110
|
<Notification color="info">
|
|
27
111
|
Components below follow the current Bulma color scheme.
|
|
28
112
|
</Notification>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
</Button>
|
|
35
|
-
<Button onClick={() => setMode('system')}>System</Button>
|
|
113
|
+
{/* One setting and its state: the label stays "Dark mode", and
|
|
114
|
+
ToggleButton says whether it is on through aria-pressed. */}
|
|
115
|
+
<ToggleButton pressed={mode === 'dark'} onPressedChange={setDark}>
|
|
116
|
+
Dark mode
|
|
117
|
+
</ToggleButton>
|
|
36
118
|
</Box>
|
|
37
119
|
</Theme>
|
|
38
120
|
);
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
// Custom brand theme for an app built on @allxsmith/bestax-bulma.
|
|
2
2
|
// Copy this to your app and adjust the HSL values to your brand.
|
|
3
3
|
//
|
|
4
|
-
// Bulma 1.x
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// Bulma 1.x builds each color from a hue/saturation/lightness trio
|
|
5
|
+
// (--bulma-<color>-h / -s / -l). Overriding the trio recolors the color
|
|
6
|
+
// itself, and its shades and variants take the new hue and saturation at the
|
|
7
|
+
// lightnesses the stylesheet was built with.
|
|
8
|
+
// The <Theme> component writes those CSS variables for you:
|
|
7
9
|
// - isRoot -> inject the variables globally at :root (use once, at the app root)
|
|
8
10
|
// - without isRoot -> wrap children in a <div> and scope the variables to it
|
|
11
|
+
//
|
|
12
|
+
// Of those built lightnesses, the label on a solid color
|
|
13
|
+
// (--bulma-<color>-invert-l) and the color's text on the page
|
|
14
|
+
// (--bulma-<color>-on-scheme-l, which for `link` is every link) were picked
|
|
15
|
+
// for contrast. A color that reads lighter or darker than the stylesheet's
|
|
16
|
+
// own needs those set in your CSS too, which Theme does not do;
|
|
17
|
+
// references/css-variables.md ("Colors") says how.
|
|
9
18
|
import type { ReactNode } from 'react';
|
|
10
19
|
import {
|
|
11
20
|
Theme,
|
|
@@ -25,10 +34,6 @@ export function ThemedApp({ children }: { children: ReactNode }) {
|
|
|
25
34
|
primaryH="265"
|
|
26
35
|
primaryS="65%"
|
|
27
36
|
primaryL="55%"
|
|
28
|
-
// A matching link color.
|
|
29
|
-
linkH="200"
|
|
30
|
-
linkS="80%"
|
|
31
|
-
linkL="45%"
|
|
32
37
|
// Anything without a named prop goes through `bulmaVars`, keyed by the real
|
|
33
38
|
// --bulma-* variable names (string values).
|
|
34
39
|
bulmaVars={{
|
|
@@ -47,7 +52,7 @@ export function ThemedApp({ children }: { children: ReactNode }) {
|
|
|
47
52
|
// 2) Scoped override — theme just one section; the rest of the app is untouched.
|
|
48
53
|
export function PromoPanel() {
|
|
49
54
|
return (
|
|
50
|
-
<Theme primaryH="16" primaryS="85%" primaryL="
|
|
55
|
+
<Theme primaryH="16" primaryS="85%" primaryL="40%">
|
|
51
56
|
<Box>
|
|
52
57
|
<Title size="4">Limited offer</Title>
|
|
53
58
|
<Notification color="primary">
|