@mindlogic-ai/logician-ui 5.0.0-alpha.1 → 5.0.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Badge/Badge.d.ts.map +1 -1
- package/dist/components/Badge/Badge.js +5 -3
- package/dist/components/Badge/Badge.js.map +1 -1
- package/dist/components/Badge/Badge.mjs +5 -3
- package/dist/components/Badge/Badge.mjs.map +1 -1
- package/dist/components/Badge/Badge.types.d.ts +7 -5
- package/dist/components/Badge/Badge.types.d.ts.map +1 -1
- package/dist/components/LineGraph/seriesColors.d.ts +2 -0
- package/dist/components/LineGraph/seriesColors.d.ts.map +1 -1
- package/dist/components/LineGraph/seriesColors.js.map +1 -1
- package/dist/components/LineGraph/seriesColors.mjs.map +1 -1
- package/dist/components/Select/Select.d.ts +5 -1
- package/dist/components/Select/Select.d.ts.map +1 -1
- package/dist/components/Select/Select.js +2 -2
- package/dist/components/Select/Select.js.map +1 -1
- package/dist/components/Select/Select.mjs +3 -3
- package/dist/components/Select/Select.mjs.map +1 -1
- package/dist/components/Select/Select.styles.d.ts +38 -0
- package/dist/components/Select/Select.styles.d.ts.map +1 -1
- package/dist/components/Select/Select.styles.js +35 -0
- package/dist/components/Select/Select.styles.js.map +1 -1
- package/dist/components/Select/Select.styles.mjs +35 -2
- package/dist/components/Select/Select.styles.mjs.map +1 -1
- package/dist/components/Select/Select.types.d.ts +10 -0
- package/dist/components/Select/Select.types.d.ts.map +1 -1
- package/dist/components/Select/SelectField.d.ts +1 -1
- package/dist/components/Select/SelectField.d.ts.map +1 -1
- package/dist/components/Select/SelectField.js +3 -2
- package/dist/components/Select/SelectField.js.map +1 -1
- package/dist/components/Select/SelectField.mjs +3 -2
- package/dist/components/Select/SelectField.mjs.map +1 -1
- package/dist/components/Select/index.d.ts +1 -0
- package/dist/components/Select/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/Badge/Badge.stories.tsx +193 -3
- package/src/components/Badge/Badge.tsx +5 -3
- package/src/components/Badge/Badge.types.ts +7 -5
- package/src/components/Badge/Badge.usage.mdx +265 -0
- package/src/components/LineGraph/seriesColors.ts +2 -0
- package/src/components/Select/Comparison.stories.tsx +19 -0
- package/src/components/Select/Select.stories.tsx +55 -0
- package/src/components/Select/Select.styles.ts +36 -1
- package/src/components/Select/Select.tsx +13 -3
- package/src/components/Select/Select.types.ts +11 -0
- package/src/components/Select/SelectField.tsx +7 -3
- package/src/components/Select/index.ts +1 -0
- package/src/foundations/BrandColour.mdx +301 -0
- package/src/foundations/BrandColour.stories.tsx +334 -0
- package/src/guidelines/ComponentPageTemplate.mdx +84 -0
- package/src/guidelines/FeedbackActions.mdx +162 -23
- package/src/guidelines/_components/Lang.tsx +175 -0
- package/src/guidelines/_components/TokenTable.tsx +136 -0
- package/src/theme/SemanticTokens.mdx +26 -0
- package/src/theme/claude.md +3 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doc-only language switch for the Storybook guideline pages.
|
|
3
|
+
*
|
|
4
|
+
* One MDX file carries both languages: wrap each block in `<En>` / `<Ko>` and
|
|
5
|
+
* put `<LangToggle />` at the top of the page. The choice is kept in
|
|
6
|
+
* localStorage (default English) and every block on the page — and on every
|
|
7
|
+
* other guideline page — follows it.
|
|
8
|
+
*
|
|
9
|
+
* Not part of the published library: nothing in `src/index.ts` imports it,
|
|
10
|
+
* `check-component-exports.js` only scans `src/components`, and the rollup
|
|
11
|
+
* build excludes `src/guidelines/**`.
|
|
12
|
+
*/
|
|
13
|
+
import { ReactNode, useSyncExternalStore } from 'react';
|
|
14
|
+
|
|
15
|
+
type DocLang = 'en' | 'ko';
|
|
16
|
+
|
|
17
|
+
const STORAGE_KEY = 'logician-ui:docs-lang';
|
|
18
|
+
const listeners = new Set<() => void>();
|
|
19
|
+
let current: DocLang | undefined;
|
|
20
|
+
|
|
21
|
+
const read = (): DocLang => {
|
|
22
|
+
try {
|
|
23
|
+
return window.localStorage.getItem(STORAGE_KEY) === 'ko' ? 'ko' : 'en';
|
|
24
|
+
} catch {
|
|
25
|
+
return 'en';
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const write = (lang: DocLang) => {
|
|
30
|
+
try {
|
|
31
|
+
window.localStorage.setItem(STORAGE_KEY, lang);
|
|
32
|
+
} catch {
|
|
33
|
+
// Storage blocked (private mode): the switch still works for this page.
|
|
34
|
+
}
|
|
35
|
+
current = lang;
|
|
36
|
+
listeners.forEach((notify) => notify());
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const snapshot = (): DocLang => (current ??= read());
|
|
40
|
+
|
|
41
|
+
// Another tab changed it: one listener for the whole page, not one per block.
|
|
42
|
+
const onStorage = (event: StorageEvent) => {
|
|
43
|
+
if (event.key !== STORAGE_KEY) return;
|
|
44
|
+
current = read();
|
|
45
|
+
listeners.forEach((notify) => notify());
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const subscribe = (notify: () => void) => {
|
|
49
|
+
if (listeners.size === 0) window.addEventListener('storage', onStorage);
|
|
50
|
+
listeners.add(notify);
|
|
51
|
+
return () => {
|
|
52
|
+
listeners.delete(notify);
|
|
53
|
+
if (listeners.size === 0) window.removeEventListener('storage', onStorage);
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** The language the reader picked; `'en'` until they pick one. */
|
|
58
|
+
const useDocLang = (): DocLang =>
|
|
59
|
+
useSyncExternalStore(subscribe, snapshot, () => 'en');
|
|
60
|
+
|
|
61
|
+
const pick = <V,>(lang: DocLang, en: V, ko: V) => (lang === 'ko' ? ko : en);
|
|
62
|
+
|
|
63
|
+
const Block = ({ lang, children }: { lang: DocLang; children: ReactNode }) =>
|
|
64
|
+
useDocLang() === lang ? <div lang={lang}>{children}</div> : null;
|
|
65
|
+
|
|
66
|
+
/** English block. Leave a blank line after the opening tag so Markdown parses. */
|
|
67
|
+
export const En = ({ children }: { children: ReactNode }) => (
|
|
68
|
+
<Block lang="en">{children}</Block>
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
/** Korean block. Leave a blank line after the opening tag so Markdown parses. */
|
|
72
|
+
export const Ko = ({ children }: { children: ReactNode }) => (
|
|
73
|
+
<Block lang="ko">{children}</Block>
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
/** Inline text in the reader's language, for captions and table cells. */
|
|
77
|
+
export const T = ({ en, ko }: { en: ReactNode; ko: ReactNode }) => {
|
|
78
|
+
const lang = useDocLang();
|
|
79
|
+
return <span lang={lang}>{pick(lang, en, ko)}</span>;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const OPTIONS: { value: DocLang; label: string }[] = [
|
|
83
|
+
{ value: 'en', label: 'English' },
|
|
84
|
+
{ value: 'ko', label: '한국어' },
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
/** Top-of-page switch. Renders as a two-button segmented control. */
|
|
88
|
+
export const LangToggle = () => {
|
|
89
|
+
const lang = useDocLang();
|
|
90
|
+
return (
|
|
91
|
+
<div
|
|
92
|
+
role="group"
|
|
93
|
+
aria-label="Language / 언어"
|
|
94
|
+
style={{
|
|
95
|
+
display: 'inline-flex',
|
|
96
|
+
border: '1px solid #D8D2CA',
|
|
97
|
+
borderRadius: 8,
|
|
98
|
+
overflow: 'hidden',
|
|
99
|
+
margin: '0 0 16px',
|
|
100
|
+
fontSize: 13,
|
|
101
|
+
}}
|
|
102
|
+
>
|
|
103
|
+
{OPTIONS.map((option) => {
|
|
104
|
+
const active = option.value === lang;
|
|
105
|
+
return (
|
|
106
|
+
<button
|
|
107
|
+
key={option.value}
|
|
108
|
+
type="button"
|
|
109
|
+
lang={option.value}
|
|
110
|
+
aria-pressed={active}
|
|
111
|
+
onClick={() => write(option.value)}
|
|
112
|
+
style={{
|
|
113
|
+
padding: '6px 14px',
|
|
114
|
+
border: 0,
|
|
115
|
+
cursor: 'pointer',
|
|
116
|
+
fontWeight: active ? 700 : 400,
|
|
117
|
+
background: active ? '#252525' : 'transparent',
|
|
118
|
+
color: active ? '#F5EDE0' : '#252525',
|
|
119
|
+
}}
|
|
120
|
+
>
|
|
121
|
+
{option.label}
|
|
122
|
+
</button>
|
|
123
|
+
);
|
|
124
|
+
})}
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const VERDICT = {
|
|
130
|
+
do: {
|
|
131
|
+
mark: '✓',
|
|
132
|
+
en: 'Do',
|
|
133
|
+
ko: '이렇게 하세요',
|
|
134
|
+
rule: '#5E7A37',
|
|
135
|
+
ink: '#425C17',
|
|
136
|
+
},
|
|
137
|
+
dont: {
|
|
138
|
+
mark: '×',
|
|
139
|
+
en: "Don't",
|
|
140
|
+
ko: '이렇게 하지 마세요',
|
|
141
|
+
rule: '#A02525',
|
|
142
|
+
ink: '#A02525',
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/** Do / Don't caption under a `<Canvas>` specimen. */
|
|
147
|
+
export const Verdict = ({
|
|
148
|
+
kind,
|
|
149
|
+
en,
|
|
150
|
+
ko,
|
|
151
|
+
}: {
|
|
152
|
+
kind: 'do' | 'dont';
|
|
153
|
+
en: ReactNode;
|
|
154
|
+
ko: ReactNode;
|
|
155
|
+
}) => {
|
|
156
|
+
const lang = useDocLang();
|
|
157
|
+
const v = VERDICT[kind];
|
|
158
|
+
return (
|
|
159
|
+
<p
|
|
160
|
+
lang={lang}
|
|
161
|
+
style={{
|
|
162
|
+
margin: '-12px 0 28px',
|
|
163
|
+
paddingLeft: 12,
|
|
164
|
+
borderLeft: `3px solid ${v.rule}`,
|
|
165
|
+
fontSize: 14,
|
|
166
|
+
lineHeight: 1.6,
|
|
167
|
+
}}
|
|
168
|
+
>
|
|
169
|
+
<strong style={{ color: v.ink }}>
|
|
170
|
+
{v.mark} {pick(lang, v.en, v.ko)}.
|
|
171
|
+
</strong>{' '}
|
|
172
|
+
{pick(lang, en, ko)}
|
|
173
|
+
</p>
|
|
174
|
+
);
|
|
175
|
+
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doc-only token table for guideline pages: one row per semantic token, with
|
|
3
|
+
* its light and dark value resolved from the shipped theme
|
|
4
|
+
* (`getSwatchGroups` in `src/theme/paletteSwatches.ts`). No hex is written by
|
|
5
|
+
* hand, so a palette change shows up here on the next build.
|
|
6
|
+
*
|
|
7
|
+
* Render it inside a story (it uses Chakra), then pull the story into MDX with
|
|
8
|
+
* `<Canvas of={…} />` or `<Story of={…} />`. Not exported from the library.
|
|
9
|
+
*/
|
|
10
|
+
import type { ReactNode } from 'react';
|
|
11
|
+
import { Box, Grid } from '@chakra-ui/react';
|
|
12
|
+
import { readableColor } from 'polished';
|
|
13
|
+
|
|
14
|
+
import { getSwatchGroups, Swatch } from '../../theme/paletteSwatches';
|
|
15
|
+
import { T } from './Lang';
|
|
16
|
+
|
|
17
|
+
const SWATCHES: ReadonlyMap<string, Swatch> = new Map(
|
|
18
|
+
getSwatchGroups().flatMap(([, swatches]) =>
|
|
19
|
+
swatches.map((swatch) => [swatch.path, swatch] as const)
|
|
20
|
+
)
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
/** Light and dark hex of a public semantic token. Throws on an unknown name. */
|
|
24
|
+
const resolveToken = (path: string): Swatch => {
|
|
25
|
+
const swatch = SWATCHES.get(path);
|
|
26
|
+
if (!swatch) throw new Error(`Unknown semantic colour token: ${path}`);
|
|
27
|
+
return swatch;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Label colour for a chip: brand black or white, whichever reads on it. */
|
|
31
|
+
const labelOn = (hex: string) =>
|
|
32
|
+
readableColor(hex, '#252525', '#FFFFFF', false);
|
|
33
|
+
|
|
34
|
+
const Chip = ({ hex }: { hex: string }) => {
|
|
35
|
+
const transparent = !/^#/.test(hex);
|
|
36
|
+
return (
|
|
37
|
+
<Box
|
|
38
|
+
// Literal values, not tokens: the chip shows one fixed mode's value
|
|
39
|
+
// whatever mode the page is in.
|
|
40
|
+
style={{
|
|
41
|
+
background: transparent ? undefined : hex,
|
|
42
|
+
color: transparent ? undefined : labelOn(hex),
|
|
43
|
+
}}
|
|
44
|
+
borderWidth="1px"
|
|
45
|
+
borderColor="border.subtle"
|
|
46
|
+
borderStyle={transparent ? 'dashed' : 'solid'}
|
|
47
|
+
borderRadius="sm"
|
|
48
|
+
px={2}
|
|
49
|
+
py={1.5}
|
|
50
|
+
fontFamily="mono"
|
|
51
|
+
fontSize="11px"
|
|
52
|
+
minW="88px"
|
|
53
|
+
>
|
|
54
|
+
{hex.replace('#', '')}
|
|
55
|
+
</Box>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
type TokenRow = {
|
|
60
|
+
/** Semantic token path, e.g. `primary.main`. */
|
|
61
|
+
token: string;
|
|
62
|
+
/** What the token is for, in the reader's language. */
|
|
63
|
+
use?: ReactNode;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const HEADERS: ReactNode[] = [
|
|
67
|
+
<T key="token" en="Token" ko="토큰" />,
|
|
68
|
+
<T key="light" en="Light" ko="라이트" />,
|
|
69
|
+
<T key="dark" en="Dark" ko="다크" />,
|
|
70
|
+
<T key="use" en="Use" ko="쓰는 곳" />,
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Token | light | dark | use. Pass `rows` as token names or `{ token, use }`;
|
|
75
|
+
* `use` is usually a `<T en ko />` so it follows the page language.
|
|
76
|
+
*/
|
|
77
|
+
export const TokenTable = ({ rows }: { rows: (string | TokenRow)[] }) => {
|
|
78
|
+
const normalised = rows.map((row) =>
|
|
79
|
+
typeof row === 'string' ? { token: row } : row
|
|
80
|
+
);
|
|
81
|
+
const hasUse = normalised.some((row) => row.use);
|
|
82
|
+
return (
|
|
83
|
+
<Grid
|
|
84
|
+
templateColumns={{
|
|
85
|
+
base: 'minmax(0,1fr) auto auto',
|
|
86
|
+
md: hasUse
|
|
87
|
+
? 'minmax(160px,auto) auto auto minmax(0,1fr)'
|
|
88
|
+
: 'minmax(160px,auto) auto auto',
|
|
89
|
+
}}
|
|
90
|
+
columnGap={3}
|
|
91
|
+
rowGap={1.5}
|
|
92
|
+
alignItems="center"
|
|
93
|
+
justifyContent="start"
|
|
94
|
+
fontSize="13px"
|
|
95
|
+
color="fg.default"
|
|
96
|
+
maxW="100%"
|
|
97
|
+
>
|
|
98
|
+
{HEADERS.slice(0, hasUse ? 4 : 3).map((header, i) => (
|
|
99
|
+
<Box
|
|
100
|
+
key={i}
|
|
101
|
+
fontWeight="bold"
|
|
102
|
+
color="fg.subtle"
|
|
103
|
+
display={i === 3 ? { base: 'none', md: 'block' } : undefined}
|
|
104
|
+
>
|
|
105
|
+
{header}
|
|
106
|
+
</Box>
|
|
107
|
+
))}
|
|
108
|
+
{normalised.map(({ token, use }) => {
|
|
109
|
+
const { light, dark } = resolveToken(token);
|
|
110
|
+
return (
|
|
111
|
+
<Box key={token} display="contents">
|
|
112
|
+
<Box fontFamily="mono" fontSize="12px" wordBreak="break-all">
|
|
113
|
+
{token}
|
|
114
|
+
{use && (
|
|
115
|
+
<Box
|
|
116
|
+
display={{ base: 'block', md: 'none' }}
|
|
117
|
+
fontFamily="body"
|
|
118
|
+
color="fg.subtle"
|
|
119
|
+
>
|
|
120
|
+
{use}
|
|
121
|
+
</Box>
|
|
122
|
+
)}
|
|
123
|
+
</Box>
|
|
124
|
+
<Chip hex={light} />
|
|
125
|
+
<Chip hex={dark} />
|
|
126
|
+
{hasUse && (
|
|
127
|
+
<Box display={{ base: 'none', md: 'block' }} color="fg.muted">
|
|
128
|
+
{use}
|
|
129
|
+
</Box>
|
|
130
|
+
)}
|
|
131
|
+
</Box>
|
|
132
|
+
);
|
|
133
|
+
})}
|
|
134
|
+
</Grid>
|
|
135
|
+
);
|
|
136
|
+
};
|
|
@@ -50,6 +50,32 @@ Emphasis is weight, not colour: headings use `fg.default`, never `primary.*`.
|
|
|
50
50
|
Status colours mean status — don't borrow `success`/`warning` for decoration;
|
|
51
51
|
that is what `category.*` is for.
|
|
52
52
|
|
|
53
|
+
## Blue means data and links
|
|
54
|
+
|
|
55
|
+
Blue is the one hue BAZE gives a meaning outside status: **data and links**.
|
|
56
|
+
Use it for both, and for nothing decorative.
|
|
57
|
+
|
|
58
|
+
- **Links** — `fg.link` / `fg.linkHover` (blue ink, #48607A / #AFC7E1), in chat
|
|
59
|
+
messages, markdown and app copy alike. Never ink-black or coral for a link.
|
|
60
|
+
- **Data visualisation** — usage/share bars, single-measure bar and line charts,
|
|
61
|
+
maps, heatmaps, progress-by-share: a **blue monochrome ramp**, never a
|
|
62
|
+
rainbow and never a grey ramp. Steps differ in lightness only (the saturated
|
|
63
|
+
`blue.base` is left out); the largest value gets the strongest step.
|
|
64
|
+
|
|
65
|
+
| Step | Light | Dark |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| 1 (largest) | `#48607A` (`blue.ink`) · white label | `#AFC7E1` (`blue.inkDark`) · ink label |
|
|
68
|
+
| 2 | `#7492B3` · ink label | `#7492B3` · ink label |
|
|
69
|
+
| 3 | `#AFC7E1` · ink label | `#48607A` · white label |
|
|
70
|
+
| 4 | `#DFEBF9` (`blue.surface`) · ink label | `#232F3D` (`blue.surfaceDark`) · white label |
|
|
71
|
+
|
|
72
|
+
Pick each label by measured contrast (≥ 4.5:1), not by step number.
|
|
73
|
+
A chart that must tell several *categories* apart (multi-series lines) keeps
|
|
74
|
+
`CHART_SERIES_HUES` in `LineGraph/seriesColors.ts`, which leaves blue out so
|
|
75
|
+
no series reads as a link or an info state.
|
|
76
|
+
`info` is also blue; status always comes with an icon and words, so a blue
|
|
77
|
+
chart is never mistaken for an info notice.
|
|
78
|
+
|
|
53
79
|
## Modes
|
|
54
80
|
|
|
55
81
|
Every role flips with the colour mode (`_light` / `_dark`) and has a
|
package/src/theme/claude.md
CHANGED
|
@@ -249,6 +249,9 @@ follow the scheme and high contrast. Base font size is 14px.
|
|
|
249
249
|
4. Beige (`secondary.surface*`) is for small surfaces (bubbles, soft
|
|
250
250
|
badges/tags, the modal header band), never rows or panels.
|
|
251
251
|
5. Disabled = `disabled.*`, not opacity. Focus = `focusRing`.
|
|
252
|
+
6. Blue means data and links: links are `fg.link`; data visualisation
|
|
253
|
+
(usage bars, single-measure charts, maps) is a blue monochrome ramp, never a
|
|
254
|
+
rainbow or grey ramp. Values: SemanticTokens.mdx → "Blue means data and links".
|
|
252
255
|
|
|
253
256
|
## Extending the Theme
|
|
254
257
|
|