@mindlogic-ai/logician-ui 5.0.0-alpha.26 → 5.0.0-alpha.28

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.
Files changed (34) hide show
  1. package/MIGRATION-5.0.md +2 -1
  2. package/dist/components/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  3. package/dist/components/SegmentedControl/SegmentedControl.js +24 -11
  4. package/dist/components/SegmentedControl/SegmentedControl.js.map +1 -1
  5. package/dist/components/SegmentedControl/SegmentedControl.mjs +24 -11
  6. package/dist/components/SegmentedControl/SegmentedControl.mjs.map +1 -1
  7. package/dist/components/Switch/SwitchControl.d.ts.map +1 -1
  8. package/dist/components/Switch/SwitchControl.js +10 -7
  9. package/dist/components/Switch/SwitchControl.js.map +1 -1
  10. package/dist/components/Switch/SwitchControl.mjs +10 -7
  11. package/dist/components/Switch/SwitchControl.mjs.map +1 -1
  12. package/dist/theme/colors.d.ts +2 -2
  13. package/dist/theme/colors.d.ts.map +1 -1
  14. package/dist/theme/colors.js +9 -1
  15. package/dist/theme/colors.js.map +1 -1
  16. package/dist/theme/colors.mjs +9 -1
  17. package/dist/theme/colors.mjs.map +1 -1
  18. package/dist/theme/palette.generated.d.ts +24 -0
  19. package/dist/theme/palette.generated.d.ts.map +1 -1
  20. package/dist/theme/palette.generated.js +24 -0
  21. package/dist/theme/palette.generated.js.map +1 -1
  22. package/dist/theme/palette.generated.mjs +24 -0
  23. package/dist/theme/palette.generated.mjs.map +1 -1
  24. package/package.json +1 -1
  25. package/src/components/SegmentedControl/SegmentedControl.tsx +25 -11
  26. package/src/components/Switch/Switch.stories.tsx +2 -2
  27. package/src/components/Switch/SwitchControl.tsx +10 -7
  28. package/src/foundations/BrandColour.mdx +34 -0
  29. package/src/foundations/BrandColour.stories.tsx +126 -2
  30. package/src/theme/SemanticTokens.mdx +35 -1
  31. package/src/theme/claude.md +8 -5
  32. package/src/theme/colors.ts +11 -1
  33. package/src/theme/contrast.test.ts +10 -0
  34. package/src/theme/palette.generated.ts +24 -0
@@ -1,13 +1,19 @@
1
- import { Flex, Stack } from '@chakra-ui/react';
1
+ import { Box, Flex, Stack } from '@chakra-ui/react';
2
2
  import { Meta, StoryObj } from '@storybook/react';
3
3
 
4
4
  import { Badge } from '../components/Badge';
5
5
  import { Button } from '../components/Button';
6
6
  import { Card } from '../components/Card';
7
7
  import { CountBadge } from '../components/CountBadge';
8
- import { SendIcon, Sparkles } from '../components/Icon';
8
+ import {
9
+ SendIcon,
10
+ Sparkles,
11
+ StarFilledIcon,
12
+ StarIcon,
13
+ } from '../components/Icon';
9
14
  import { IconButton } from '../components/IconButton';
10
15
  import { Tag } from '../components/Tag';
16
+ import { Switch } from '../components/Switch';
11
17
  import { Link, Text } from '../components/Typography';
12
18
  import { T } from '../guidelines/_components/Lang';
13
19
  import { TokenTable } from '../guidelines/_components/TokenTable';
@@ -267,6 +273,124 @@ const STATUS = [
267
273
  { s: 'info', en: 'This group only', ko: '이 그룹만' },
268
274
  ] as const;
269
275
 
276
+ /**
277
+ * A favourite toggle as it sits on a thumbnail: a bg.surface circle over the
278
+ * image, the star inside it. `fill` is the colour of the filled (marked) star;
279
+ * the unmarked outline is always `fg.icon`.
280
+ */
281
+ const StarPair = ({ fill }: { fill: string }) => (
282
+ <Flex gap={3}>
283
+ {[true, false].map((marked) => (
284
+ <Box
285
+ key={String(marked)}
286
+ position="relative"
287
+ w="96px"
288
+ h="64px"
289
+ borderRadius="md"
290
+ bg="bg.muted"
291
+ borderWidth="1px"
292
+ borderColor="border.subtle"
293
+ >
294
+ <Flex
295
+ position="absolute"
296
+ top={1.5}
297
+ right={1.5}
298
+ w="28px"
299
+ h="28px"
300
+ borderRadius="full"
301
+ bg="bg.surface"
302
+ align="center"
303
+ justify="center"
304
+ aria-label={marked ? '즐겨찾기됨' : '즐겨찾기 안 됨'}
305
+ role="img"
306
+ >
307
+ {marked ? (
308
+ <StarFilledIcon boxSize="sm" color={fill} />
309
+ ) : (
310
+ <StarIcon boxSize="sm" color="fg.icon" />
311
+ )}
312
+ </Flex>
313
+ </Box>
314
+ ))}
315
+ </Flex>
316
+ );
317
+
318
+ /**
319
+ * State is neither status nor brand: `control.on` for a control that is on,
320
+ * `mark.starred` for something the user marked to keep. The "Don't" row is
321
+ * the `primary.main` star this replaces: the same ink as the outline beside it.
322
+ */
323
+ export const State: Story = {
324
+ render: () => (
325
+ <Stack gap={5}>
326
+ <Flex gap={8} wrap="wrap" align="flex-start">
327
+ <Stack gap={2}>
328
+ <Text textStyle="subtext" color="fg.muted">
329
+ <T en="Switch: off · on" ko="Switch: 꺼짐 · 켜짐" />
330
+ </Text>
331
+ <Flex gap={4} align="center">
332
+ <Switch aria-label="꺼진 스위치">
333
+ <Switch.Control />
334
+ </Switch>
335
+ <Switch defaultChecked aria-label="켜진 스위치">
336
+ <Switch.Control />
337
+ </Switch>
338
+ </Flex>
339
+ </Stack>
340
+ <Stack gap={2}>
341
+ <Text textStyle="subtext" color="fg.muted">
342
+ <T
343
+ en="Do: mark.starred · fg.icon outline"
344
+ ko="이렇게: mark.starred · fg.icon 외곽선"
345
+ />
346
+ </Text>
347
+ <StarPair fill="mark.starred" />
348
+ </Stack>
349
+ <Stack gap={2}>
350
+ <Text textStyle="subtext" color="fg.muted">
351
+ <T
352
+ en="Don't: primary.main · same ink as the outline"
353
+ ko="이렇게 하지 않기: primary.main · 외곽선과 같은 잉크"
354
+ />
355
+ </Text>
356
+ <StarPair fill="primary.main" />
357
+ </Stack>
358
+ </Flex>
359
+ <TokenTable
360
+ rows={[
361
+ {
362
+ token: 'control.on',
363
+ use: (
364
+ <T
365
+ en="On state: Switch track, selected toggle chip"
366
+ ko="켜짐 상태: Switch 트랙, 선택된 토글 칩"
367
+ />
368
+ ),
369
+ },
370
+ {
371
+ token: 'control.onFg',
372
+ use: (
373
+ <T
374
+ en="Thumb or label on control.on"
375
+ ko="control.on 위 손잡이·글자"
376
+ />
377
+ ),
378
+ },
379
+ {
380
+ token: 'mark.starred',
381
+ use: (
382
+ <T
383
+ en="Marked to keep: favourite, saved template, bookmark"
384
+ ko="사용자가 표시한 항목: 즐겨찾기, 저장한 템플릿, 북마크"
385
+ />
386
+ ),
387
+ },
388
+ ]}
389
+ />
390
+ </Stack>
391
+ ),
392
+ };
393
+
270
394
  export const Status: Story = {
271
395
  render: () => (
272
396
  <Stack gap={5}>
@@ -15,7 +15,7 @@ Values and contrast for every token: **Setup/Theme/BAZE Palette**.
15
15
  | --- | --- | --- |
16
16
  | Raw scale (colour names) | `ink.1–12`, `inkDark.1–12`, `sand.*`, `slate.*`, `coral.*`, `red.base` … | never in components |
17
17
  | Semantic step (role names) | `neutral.1–12`, `secondary.1–12` | a specific neutral step no role names |
18
- | Semantic role | `bg.*`, `layer.*`, `fg.*`, `border.*`, `primary.*`, `secondary.*`, `action.*`, `send.*`, `count.*`, `disabled.*`, `focus.ring`, status, `tooltip.*`, `category.*`, `data.*`, `diverging.*` | everything else |
18
+ | Semantic role | `bg.*`, `layer.*`, `fg.*`, `border.*`, `primary.*`, `secondary.*`, `action.*`, `send.*`, `count.*`, `control.*`, `mark.starred`, `disabled.*`, `focus.ring`, status, `tooltip.*`, `category.*`, `data.*`, `diverging.*` | everything else |
19
19
 
20
20
  ```ts
21
21
  createLogicianTheme() // BAZE: { neutral: 'ink', secondary: 'sand' }
@@ -42,6 +42,8 @@ createLogicianTheme({ neutral: 'slate' }) // cool gray — app code unchanged
42
42
  | Run button (black + coral icon) · small coral marks | `action.bg` / `.fg` / `.iconOnSolid` · `action.mark` |
43
43
  | Send button, Button `accent` | `send.bg` / `.bgHover` / `.bgActive` / `.fg` |
44
44
  | Unread count | `count.bg` / `count.fg` |
45
+ | On/off state: Switch on, selected toggle chip · thumb or label on it | `control.on` · `control.onFg` |
46
+ | User-marked item: favourite star, saved template, bookmark (filled) | `mark.starred` (the unmarked outline stays `fg.icon`) |
45
47
  | Disabled (no opacity) | `disabled.bg` / `disabled.fg` · fill-less ghost button: `disabled.fgGhost` |
46
48
  | Status | `danger` · `success` · `warning` · `info` → `.bg` `.border` `.fg` `.solid` `.onSolid` |
47
49
  | Decorative category (tiles, avatars, tags, charts) | `category.{hue}.surface` / `.fg` |
@@ -52,6 +54,38 @@ Emphasis is weight, not colour: headings use `fg.default`, never `primary.*`.
52
54
  Status colours mean status — don't borrow `success`/`warning` for decoration;
53
55
  that is what `category.*` is for.
54
56
 
57
+ ## State: on and starred
58
+
59
+ **Binary on/off state uses `control.on`; user-marked items (star/bookmark) use
60
+ `mark.starred`; never `primary`/black/white or a status colour for state.**
61
+
62
+ - **`control.on`** fills a control that is on: the Switch track, a selected
63
+ toggle chip, any on/off control. Whatever is drawn on it (the thumb, the
64
+ chip label) is `control.onFg`. It is green today (#30862D light / #8FC46E
65
+ dark), the value Switch read from `success.solid` before, but it is not a
66
+ status: "on" does not mean "succeeded", and the two may move apart.
67
+ - **`mark.starred`** fills something the user marked to keep: a favourite, a
68
+ saved template, a starred item, a bookmark. The filled icon takes
69
+ `mark.starred`; the unmarked outline stays `fg.icon`, so selected and
70
+ unselected differ in colour as well as in shape.
71
+ - **Why not `primary`.** `primary.main` is the same ink (#252525 light, the
72
+ pale sand in dark) as the outline icon next to it, so a filled star and an
73
+ empty one could only be told apart by shape. A status colour would say
74
+ something about the item that is not true.
75
+ - **Not coral, blue or teal/mint.** Coral is send, run, AI-working and the
76
+ count badge; blue is data and links; teal and mint are not product UI.
77
+ - Checkbox stays `primary.main` when checked.
78
+
79
+ | Token | Light | Dark | High contrast (light / dark) |
80
+ | --- | --- | --- | --- |
81
+ | `control.on` | `#30862D` (4.59:1 on white) | `#8FC46E` (7.22:1 on `bg.surface`) | `#036805` / `#8FC46E` |
82
+ | `control.onFg` | `#FFFFFF` | `#1B1B1B` | same |
83
+ | `mark.starred` | `#B78100` (3.41:1 on white, 3.01:1 on `layer.1`) | `#E4AC44` (`yellow.base`, 7.22:1 on `bg.surface`) | `#976300` (4.51:1 on `layer.1`) / `#E4AC44` |
84
+
85
+ `mark.starred` is the yellow hue (BAZE's gold). `yellow.base` is only 2.04:1
86
+ on white, so light mode darkens it in OKLCH (same hue) until it clears 3:1 on
87
+ every light layer; dark mode uses `yellow.base` itself.
88
+
55
89
  ## Blue means data and links
56
90
 
57
91
  Blue is the one hue BAZE gives a meaning outside status: **data and links**.
@@ -143,11 +143,11 @@ is the maintainer's view.
143
143
 
144
144
  ### Layers and files
145
145
 
146
- | Layer | Where | Rule |
147
- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------- |
148
- | Raw scales `ink`/`inkDark`, `sand`/`sandDark`, `slate`/`slateDark` (1–12), `coral`, 12 system hues `{base, baseDark, hc, hcDark, surface, ink, surfaceDark, inkDark}`, `dataviz` | `palette.generated.ts` → `colors` in `colors.ts` | Components and recipes **never** read them |
149
- | Semantic steps `neutral.1–12`, `secondary.1–12` | `createSemanticColors()` | Filled by the theme option (`neutral: 'ink' \| 'slate'`, `secondary: 'sand'`) |
150
- | Roles (`bg.*`, `layer.*`, `fg.*`, `border.*`, `primary.*`, `secondary.*`, `action.*`, `send.*`, `count.*`, `disabled.*`, `focus.ring`, status, `tooltip.*`, `category.*`, `data.*`, `diverging.*`) | `ROLE_TOKEN_PATHS` + `createSemanticColors()` | The only colours a component reads |
146
+ | Layer | Where | Rule |
147
+ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------- |
148
+ | Raw scales `ink`/`inkDark`, `sand`/`sandDark`, `slate`/`slateDark` (1–12), `coral`, 12 system hues `{base, baseDark, hc, hcDark, surface, ink, surfaceDark, inkDark}`, `dataviz` | `palette.generated.ts` → `colors` in `colors.ts` | Components and recipes **never** read them |
149
+ | Semantic steps `neutral.1–12`, `secondary.1–12` | `createSemanticColors()` | Filled by the theme option (`neutral: 'ink' \| 'slate'`, `secondary: 'sand'`) |
150
+ | Roles (`bg.*`, `layer.*`, `fg.*`, `border.*`, `primary.*`, `secondary.*`, `action.*`, `send.*`, `count.*`, `control.*`, `mark.starred`, `disabled.*`, `focus.ring`, status, `tooltip.*`, `category.*`, `data.*`, `diverging.*`) | `ROLE_TOKEN_PATHS` + `createSemanticColors()` | The only colours a component reads |
151
151
 
152
152
  - **Values come from the generator.** `scripts/palette/generate.mjs` ports the
153
153
  designer prototype (OKLCH, contrast-targeted steps) and `--check`s it against
@@ -255,6 +255,9 @@ follow the scheme and high contrast. Base font size is 14px.
255
255
  1. Read only role tokens; reach for `neutral.N` only when no role names the job.
256
256
  2. Emphasis is weight, not colour — never `primary.*` for text.
257
257
  3. Status colours carry status meaning; decoration uses `category.*`.
258
+ State is neither: binary on/off state uses `control.on` (+ `control.onFg`
259
+ for the thumb or label on it); user-marked items (star/bookmark) use
260
+ `mark.starred`; never `primary`/black/white or a status colour for state.
258
261
  4. Beige (`secondary.surface*`) is for small surfaces (bubbles, soft
259
262
  badges/tags, the modal header band), never rows or panels.
260
263
  5. Disabled = `disabled.*`, not opacity. Focus = `focusRing`.
@@ -16,7 +16,8 @@
16
16
  * `createLogicianTheme({ neutral, secondary })` decides which raw scale fills
17
17
  * them (default `ink` / `sand`).
18
18
  * 3. **Semantic roles** — `bg.*`, `layer.*`, `fg.*`, `border.*`, `primary.*`,
19
- * `secondary.*`, `action.*`, `send.*`, `count.*`, `disabled.*`, `focus.ring`,
19
+ * `secondary.*`, `action.*`, `send.*`, `count.*`, `control.*` (on/off
20
+ * state), `mark.starred` (user-marked items), `disabled.*`, `focus.ring`,
20
21
  * the four status families, `tooltip.*`, `category.{hue}.*` (plus the
21
22
  * nameless `category.neutral.*`), the `bg.logo` / `fg.onLogo` tile, and the
22
23
  * data-visualisation fills `data.1–4` and `diverging.{pos,neg}.1–3` /
@@ -208,6 +209,13 @@ const PATHS: Record<FlatKey, string> = {
208
209
  'send-fg': 'send.fg',
209
210
  'count-bg': 'count.bg',
210
211
  'count-fg': 'count.fg',
212
+ // State, not status: a binary control that is on (Switch, selected toggle
213
+ // chip) and the thing on it (thumb, chip label); something the user marked
214
+ // to keep (favourite star, bookmark). Never primary/black/white or a status
215
+ // colour for either.
216
+ 'control-on': 'control.on',
217
+ 'control-onFg': 'control.onFg',
218
+ 'mark-starred': 'mark.starred',
211
219
  'disabled-bg': 'disabled.bg',
212
220
  'disabled-fg': 'disabled.fg',
213
221
  'disabled-fgGhost': 'disabled.fgGhost',
@@ -465,6 +473,8 @@ export type SemanticColorToken =
465
473
  | `action.${'mark' | 'iconOnSolid' | 'bg' | 'bgHover' | 'bgActive' | 'fg' | 'border'}`
466
474
  | `send.${'bg' | 'bgHover' | 'bgActive' | 'fg'}`
467
475
  | `count.${'bg' | 'fg'}`
476
+ | `control.${'on' | 'onFg'}`
477
+ | 'mark.starred'
468
478
  | `disabled.${'bg' | 'fg' | 'fgGhost'}`
469
479
  | 'focus.ring'
470
480
  | `${StatusFamily}.${'bg' | 'border' | 'fg' | 'solid' | 'onSolid'}`
@@ -47,6 +47,10 @@ const PAIRS: [string, string, Kind][] = [
47
47
  ['action-mark', bg, 'nontext'],
48
48
  ['primary-main', bg, 'nontext'],
49
49
  ['danger-fg', bg, 'nontext'],
50
+ // An on Switch track / selected chip against the ground it sits on,
51
+ // and a starred mark (a filled icon) on every layer (WCAG 1.4.11).
52
+ ['control-on', bg, 'nontext'],
53
+ ['mark-starred', bg, 'nontext'],
50
54
  ] as [string, string, Kind][]
51
55
  ),
52
56
  ['fg-placeholder', 'bg-surface', 'text'],
@@ -65,6 +69,10 @@ const PAIRS: [string, string, Kind][] = [
65
69
  ['fg-muted', 'bg-subtle', 'text'],
66
70
  ['fg-icon', 'bg-muted', 'nontext'],
67
71
  ['fg-icon', 'bg-subtle', 'nontext'],
72
+ // The favourite star sits on a bg.surface circle over thumbnails, and on
73
+ // bg.muted cards.
74
+ ['mark-starred', 'bg-surface', 'nontext'],
75
+ ['mark-starred', 'bg-muted', 'nontext'],
68
76
  ['fg-default', 'secondary-surface', 'text'],
69
77
  ['secondary-fgStrong', 'secondary-surface', 'text'],
70
78
  ['secondary-fgStrong', 'secondary-surfaceStrong', 'text'],
@@ -86,6 +94,8 @@ const PAIRS: [string, string, Kind][] = [
86
94
  ['danger-onSolid', 'danger-solid', 'text'],
87
95
  ['success-fg', 'success-bg', 'text'],
88
96
  ['success-onSolid', 'success-solid', 'text'],
97
+ // `text`: a selected toggle chip puts its label on control.on.
98
+ ['control-onFg', 'control-on', 'text'],
89
99
  ['warning-fg', 'warning-bg', 'text'],
90
100
  ['warning-onSolid', 'warning-solid', 'text'],
91
101
  ['info-fg', 'info-bg', 'text'],
@@ -316,6 +316,9 @@ export const SCHEMES = {
316
316
  "send-fg": "#252525",
317
317
  "count-bg": "#D44239",
318
318
  "count-fg": "#FFFFFF",
319
+ "control-on": "#30862D",
320
+ "control-onFg": "#FFFFFF",
321
+ "mark-starred": "#B78100",
319
322
  "disabled-bg": "#EDEDED",
320
323
  "disabled-fg": "#8A8A8A",
321
324
  "disabled-fgGhost": "#C2C2C2",
@@ -407,6 +410,9 @@ export const SCHEMES = {
407
410
  "send-fg": "#252525",
408
411
  "count-bg": "#D44239",
409
412
  "count-fg": "#FFFFFF",
413
+ "control-on": "#8FC46E",
414
+ "control-onFg": "#1B1B1B",
415
+ "mark-starred": "#E4AC44",
410
416
  "disabled-bg": "#2C2C2C",
411
417
  "disabled-fg": "#737373",
412
418
  "disabled-fgGhost": "#4A4A4A",
@@ -498,6 +504,9 @@ export const SCHEMES = {
498
504
  "send-fg": "#252525",
499
505
  "count-bg": "#B01919",
500
506
  "count-fg": "#FFFFFF",
507
+ "control-on": "#036805",
508
+ "control-onFg": "#FFFFFF",
509
+ "mark-starred": "#976300",
501
510
  "disabled-bg": "#EDEDED",
502
511
  "disabled-fg": "#8A8A8A",
503
512
  "disabled-fgGhost": "#C2C2C2",
@@ -589,6 +598,9 @@ export const SCHEMES = {
589
598
  "send-fg": "#252525",
590
599
  "count-bg": "#B01919",
591
600
  "count-fg": "#FFFFFF",
601
+ "control-on": "#8FC46E",
602
+ "control-onFg": "#1B1B1B",
603
+ "mark-starred": "#E4AC44",
592
604
  "disabled-bg": "#2C2C2C",
593
605
  "disabled-fg": "#737373",
594
606
  "disabled-fgGhost": "#4A4A4A",
@@ -682,6 +694,9 @@ export const SCHEMES = {
682
694
  "send-fg": "#252525",
683
695
  "count-bg": "#D44239",
684
696
  "count-fg": "#FFFFFF",
697
+ "control-on": "#30862D",
698
+ "control-onFg": "#FFFFFF",
699
+ "mark-starred": "#B78100",
685
700
  "disabled-bg": "#EDEDED",
686
701
  "disabled-fg": "#8A8A8A",
687
702
  "disabled-fgGhost": "#C2C2C2",
@@ -773,6 +788,9 @@ export const SCHEMES = {
773
788
  "send-fg": "#252525",
774
789
  "count-bg": "#D44239",
775
790
  "count-fg": "#FFFFFF",
791
+ "control-on": "#8FC46E",
792
+ "control-onFg": "#1B1B1B",
793
+ "mark-starred": "#E4AC44",
776
794
  "disabled-bg": "#2C2C2C",
777
795
  "disabled-fg": "#737373",
778
796
  "disabled-fgGhost": "#4A4A4A",
@@ -864,6 +882,9 @@ export const SCHEMES = {
864
882
  "send-fg": "#252525",
865
883
  "count-bg": "#B01919",
866
884
  "count-fg": "#FFFFFF",
885
+ "control-on": "#036805",
886
+ "control-onFg": "#FFFFFF",
887
+ "mark-starred": "#976300",
867
888
  "disabled-bg": "#EDEDED",
868
889
  "disabled-fg": "#8A8A8A",
869
890
  "disabled-fgGhost": "#C2C2C2",
@@ -955,6 +976,9 @@ export const SCHEMES = {
955
976
  "send-fg": "#252525",
956
977
  "count-bg": "#B01919",
957
978
  "count-fg": "#FFFFFF",
979
+ "control-on": "#8FC46E",
980
+ "control-onFg": "#1B1B1B",
981
+ "mark-starred": "#E4AC44",
958
982
  "disabled-bg": "#2C2C2C",
959
983
  "disabled-fg": "#737373",
960
984
  "disabled-fgGhost": "#4A4A4A",