@mindlogic-ai/logician-ui 3.2.0-alpha.4 → 4.0.0-alpha.10
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/InlineCode/InlineCode.d.ts.map +1 -1
- package/dist/components/InlineCode/InlineCode.js +8 -1
- package/dist/components/InlineCode/InlineCode.js.map +1 -1
- package/dist/components/InlineCode/InlineCode.mjs +8 -1
- package/dist/components/InlineCode/InlineCode.mjs.map +1 -1
- package/dist/components/Loaders/PageLoader.d.ts.map +1 -1
- package/dist/components/Loaders/PageLoader.js +2 -2
- package/dist/components/Loaders/PageLoader.js.map +1 -1
- package/dist/components/Loaders/PageLoader.mjs +2 -2
- package/dist/components/Loaders/PageLoader.mjs.map +1 -1
- package/dist/components/Loaders/SectionLoader.d.ts.map +1 -1
- package/dist/components/Loaders/SectionLoader.js +2 -2
- package/dist/components/Loaders/SectionLoader.js.map +1 -1
- package/dist/components/Loaders/SectionLoader.mjs +2 -2
- package/dist/components/Loaders/SectionLoader.mjs.map +1 -1
- package/dist/components/SegmentedControl/SegmentedControl.d.ts.map +1 -1
- package/dist/components/SegmentedControl/SegmentedControl.js +23 -15
- package/dist/components/SegmentedControl/SegmentedControl.js.map +1 -1
- package/dist/components/SegmentedControl/SegmentedControl.mjs +23 -15
- package/dist/components/SegmentedControl/SegmentedControl.mjs.map +1 -1
- package/dist/components/SegmentedProgressBar/SegmentedProgressBar.d.ts.map +1 -1
- package/dist/components/SegmentedProgressBar/SegmentedProgressBar.js +6 -1
- package/dist/components/SegmentedProgressBar/SegmentedProgressBar.js.map +1 -1
- package/dist/components/SegmentedProgressBar/SegmentedProgressBar.mjs +6 -1
- package/dist/components/SegmentedProgressBar/SegmentedProgressBar.mjs.map +1 -1
- package/dist/components/Slider/SliderTrack/SliderTrack.d.ts.map +1 -1
- package/dist/components/Slider/SliderTrack/SliderTrack.js +13 -1
- package/dist/components/Slider/SliderTrack/SliderTrack.js.map +1 -1
- package/dist/components/Slider/SliderTrack/SliderTrack.mjs +13 -1
- package/dist/components/Slider/SliderTrack/SliderTrack.mjs.map +1 -1
- package/dist/components/Spinner/Spinner.d.ts.map +1 -1
- package/dist/components/Spinner/Spinner.js +7 -1
- package/dist/components/Spinner/Spinner.js.map +1 -1
- package/dist/components/Spinner/Spinner.mjs +7 -1
- package/dist/components/Spinner/Spinner.mjs.map +1 -1
- package/dist/components/Switch/SwitchControl.d.ts.map +1 -1
- package/dist/components/Switch/SwitchControl.js +7 -1
- package/dist/components/Switch/SwitchControl.js.map +1 -1
- package/dist/components/Switch/SwitchControl.mjs +7 -1
- package/dist/components/Switch/SwitchControl.mjs.map +1 -1
- package/dist/components/Tag/Tag.d.ts.map +1 -1
- package/dist/components/Tag/Tag.js +1 -1
- package/dist/components/Tag/Tag.js.map +1 -1
- package/dist/components/Tag/Tag.mjs +1 -1
- package/dist/components/Tag/Tag.mjs.map +1 -1
- package/dist/components/Tag/Tag.styles.d.ts.map +1 -1
- package/dist/components/Tag/Tag.styles.js +22 -16
- package/dist/components/Tag/Tag.styles.js.map +1 -1
- package/dist/components/Tag/Tag.styles.mjs +22 -16
- package/dist/components/Tag/Tag.styles.mjs.map +1 -1
- package/dist/components/Tree/Tree.d.ts +5 -1
- package/dist/components/Tree/Tree.d.ts.map +1 -1
- package/dist/components/Tree/Tree.types.d.ts +29 -1
- package/dist/components/Tree/Tree.types.d.ts.map +1 -1
- package/dist/components/Tree/TreeBranchIndentGuide.d.ts +5 -1
- package/dist/components/Tree/TreeBranchIndentGuide.d.ts.map +1 -1
- package/dist/components/Tree/TreeBranchIndentGuide.js +107 -8
- package/dist/components/Tree/TreeBranchIndentGuide.js.map +1 -1
- package/dist/components/Tree/TreeBranchIndentGuide.mjs +107 -8
- package/dist/components/Tree/TreeBranchIndentGuide.mjs.map +1 -1
- package/dist/components/Typography/Link.d.ts.map +1 -1
- package/dist/components/Typography/Link.js +6 -1
- package/dist/components/Typography/Link.js.map +1 -1
- package/dist/components/Typography/Link.mjs +6 -1
- package/dist/components/Typography/Link.mjs.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +0 -1
- package/dist/index.mjs.map +1 -1
- package/dist/theme/colors.d.ts +7 -1
- package/dist/theme/colors.d.ts.map +1 -1
- package/dist/theme/colors.js +12 -0
- package/dist/theme/colors.js.map +1 -1
- package/dist/theme/colors.mjs +12 -0
- package/dist/theme/colors.mjs.map +1 -1
- package/dist/theme/global.d.ts.map +1 -1
- package/dist/theme/global.js +4 -1
- package/dist/theme/global.js.map +1 -1
- package/dist/theme/global.mjs +4 -1
- package/dist/theme/global.mjs.map +1 -1
- package/dist/theme/index.d.ts +1 -1
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +8 -4
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +8 -4
- package/dist/theme/index.mjs.map +1 -1
- package/package.json +1 -3
- package/src/components/InlineCode/InlineCode.tsx +7 -0
- package/src/components/Loaders/PageLoader.tsx +3 -5
- package/src/components/Loaders/SectionLoader.tsx +3 -2
- package/src/components/SegmentedControl/SegmentedControl.stories.tsx +27 -1
- package/src/components/SegmentedControl/SegmentedControl.tsx +22 -15
- package/src/components/SegmentedProgressBar/SegmentedProgressBar.tsx +5 -1
- package/src/components/Slider/SliderTrack/SliderTrack.tsx +10 -1
- package/src/components/Spinner/Spinner.tsx +6 -1
- package/src/components/Switch/SwitchControl.tsx +6 -1
- package/src/components/Tag/Tag.styles.ts +22 -16
- package/src/components/Tag/Tag.tsx +1 -0
- package/src/components/Tree/Tree.GuideElbow.stories.tsx +220 -0
- package/src/components/Tree/Tree.types.ts +29 -1
- package/src/components/Tree/TreeBranchIndentGuide.test.tsx +213 -0
- package/src/components/Tree/TreeBranchIndentGuide.tsx +137 -11
- package/src/components/Typography/Link.tsx +5 -1
- package/src/index.ts +0 -1
- package/src/theme/Surfaces.stories.tsx +208 -0
- package/src/theme/colors.ts +13 -0
- package/src/theme/global.ts +4 -1
- package/src/theme/index.ts +8 -4
- package/dist/components/MDXEditor/MDXEditor.d.ts +0 -6
- package/dist/components/MDXEditor/MDXEditor.d.ts.map +0 -1
- package/dist/components/MDXEditor/MDXEditor.js +0 -237
- package/dist/components/MDXEditor/MDXEditor.js.map +0 -1
- package/dist/components/MDXEditor/MDXEditor.mjs +0 -235
- package/dist/components/MDXEditor/MDXEditor.mjs.map +0 -1
- package/dist/components/MDXEditor/MDXEditor.types.d.ts +0 -7
- package/dist/components/MDXEditor/MDXEditor.types.d.ts.map +0 -1
- package/dist/components/MDXEditor/index.d.ts +0 -3
- package/dist/components/MDXEditor/index.d.ts.map +0 -1
- package/src/components/MDXEditor/MDXEditor.css +0 -40
- package/src/components/MDXEditor/MDXEditor.stories.tsx +0 -56
- package/src/components/MDXEditor/MDXEditor.tsx +0 -337
- package/src/components/MDXEditor/MDXEditor.types.ts +0 -7
- package/src/components/MDXEditor/index.ts +0 -2
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { ChakraProvider } from '@chakra-ui/react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
|
|
4
|
+
import { system } from '../../theme';
|
|
5
|
+
import { createTreeCollection } from './index';
|
|
6
|
+
import { Tree } from './Tree';
|
|
7
|
+
|
|
8
|
+
interface N {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
children?: N[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const sample: N = {
|
|
15
|
+
id: 'ROOT',
|
|
16
|
+
name: '',
|
|
17
|
+
children: [{ id: 'a', name: 'A', children: [{ id: 'a1', name: 'A1' }] }],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const collection = createTreeCollection<N>({
|
|
21
|
+
rootNode: sample,
|
|
22
|
+
nodeToValue: (n) => n.id,
|
|
23
|
+
nodeToString: (n) => n.name,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const renderNode = ({
|
|
27
|
+
node,
|
|
28
|
+
nodeState,
|
|
29
|
+
}: {
|
|
30
|
+
node: N;
|
|
31
|
+
nodeState: { isBranch: boolean };
|
|
32
|
+
}) =>
|
|
33
|
+
nodeState.isBranch ? (
|
|
34
|
+
<Tree.BranchControl>
|
|
35
|
+
<Tree.BranchText>{node.name}</Tree.BranchText>
|
|
36
|
+
</Tree.BranchControl>
|
|
37
|
+
) : (
|
|
38
|
+
<Tree.Item>
|
|
39
|
+
<Tree.ItemText>{node.name}</Tree.ItemText>
|
|
40
|
+
</Tree.Item>
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
function renderGuide(elbow: boolean, extraProps: { guideColor?: string } = {}) {
|
|
44
|
+
const { container } = render(
|
|
45
|
+
<ChakraProvider value={system}>
|
|
46
|
+
<Tree.Root
|
|
47
|
+
collection={collection}
|
|
48
|
+
aria-label="t"
|
|
49
|
+
defaultExpandedValue={['a']}
|
|
50
|
+
>
|
|
51
|
+
<Tree.Tree>
|
|
52
|
+
<Tree.Node
|
|
53
|
+
indentGuide={
|
|
54
|
+
elbow ? (
|
|
55
|
+
<Tree.BranchIndentGuide elbow {...extraProps} />
|
|
56
|
+
) : (
|
|
57
|
+
<Tree.BranchIndentGuide {...extraProps} />
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
render={renderNode}
|
|
61
|
+
/>
|
|
62
|
+
</Tree.Tree>
|
|
63
|
+
</Tree.Root>
|
|
64
|
+
</ChakraProvider>
|
|
65
|
+
);
|
|
66
|
+
const guide = container.querySelector<HTMLElement>(
|
|
67
|
+
'[data-part="branch-indent-guide"]'
|
|
68
|
+
);
|
|
69
|
+
if (!guide) throw new Error('no indent guide rendered');
|
|
70
|
+
return guide.className;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// All Chakra/emotion-injected CSS, concatenated.
|
|
74
|
+
const sheetText = () =>
|
|
75
|
+
Array.from(document.querySelectorAll('style'))
|
|
76
|
+
.map((s) => s.textContent ?? '')
|
|
77
|
+
.join('\n');
|
|
78
|
+
|
|
79
|
+
// The foot rule is anchored on the emotion-generated `css-*` class
|
|
80
|
+
// (not the static `chakra-*` slot class), so target that one.
|
|
81
|
+
const styleAnchor = (className: string) => {
|
|
82
|
+
const emotion = className
|
|
83
|
+
.trim()
|
|
84
|
+
.split(/\s+/)
|
|
85
|
+
.find((c) => /^css-/.test(c));
|
|
86
|
+
if (!emotion) throw new Error(`no emotion class in: ${className}`);
|
|
87
|
+
return '\\.' + emotion;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Regression guard for the `elbow` foot. Chakra renders ONE indent guide
|
|
92
|
+
* per `branchContent` (its first child), followed by that branch's rows
|
|
93
|
+
* as siblings — so the foot must be drawn on those sibling rows, scoped
|
|
94
|
+
* to the elbow guide's own class. An earlier `:last-of-type::after`
|
|
95
|
+
* attempt silently matched nothing (the guide is never the last sibling
|
|
96
|
+
* of its type), which is exactly what these tests pin down.
|
|
97
|
+
*/
|
|
98
|
+
describe('Tree.BranchIndentGuide elbow', () => {
|
|
99
|
+
it('draws a ::before foot on the guide’s sibling rows when elbow is set', () => {
|
|
100
|
+
const cls = renderGuide(true);
|
|
101
|
+
const anchor = styleAnchor(cls);
|
|
102
|
+
const css = sheetText();
|
|
103
|
+
|
|
104
|
+
// Foot rule is scoped to THIS guide's class via the sibling combinator,
|
|
105
|
+
// for both leaf rows and branch rows — never applied globally.
|
|
106
|
+
expect(css).toMatch(
|
|
107
|
+
new RegExp(`${anchor}\\s*~\\s*\\[data-part="item"\\]::before`)
|
|
108
|
+
);
|
|
109
|
+
expect(css).toMatch(
|
|
110
|
+
new RegExp(
|
|
111
|
+
`${anchor}\\s*~\\s*\\[data-part="branch"\\]\\s*>\\s*\\[data-part="branch-control"\\]::before`
|
|
112
|
+
)
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// The foot paints a 1px cross-stroke pinned to the row's vertical
|
|
116
|
+
// centre, starting at the parent rail column.
|
|
117
|
+
const footRule =
|
|
118
|
+
css.match(
|
|
119
|
+
new RegExp(
|
|
120
|
+
`${anchor}\\s*~\\s*\\[data-part="item"\\]::before\\s*\\{[^}]*\\}`
|
|
121
|
+
)
|
|
122
|
+
)?.[0] ?? '';
|
|
123
|
+
expect(footRule).toContain('content:""');
|
|
124
|
+
expect(footRule).toContain('position:absolute');
|
|
125
|
+
expect(footRule).toContain('inset-block-start:50%');
|
|
126
|
+
expect(footRule).toContain('height:1px');
|
|
127
|
+
expect(footRule).toMatch(
|
|
128
|
+
/inset-inline-start:calc\(var\(--tree-offset\) - var\(--tree-elbow-indentation\)\)/
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// Elbow mode widens per-level indentation: the sibling rows redefine
|
|
132
|
+
// the recipe's indentation-offset from the widened value, pushing the
|
|
133
|
+
// row text right, away from the elbow foot.
|
|
134
|
+
const rowVarsRule =
|
|
135
|
+
css.match(
|
|
136
|
+
new RegExp(`${anchor}\\s*~\\s*\\[data-part="item"\\]\\s*\\{[^}]*\\}`)
|
|
137
|
+
)?.[0] ?? '';
|
|
138
|
+
expect(rowVarsRule).toContain(
|
|
139
|
+
'--tree-elbow-indentation:calc(var(--tree-indentation) + 4px)'
|
|
140
|
+
);
|
|
141
|
+
expect(rowVarsRule).toContain(
|
|
142
|
+
'--tree-indentation-offset:calc(var(--tree-elbow-indentation) * var(--tree-depth))'
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('draws no foot for a plain guide (no elbow)', () => {
|
|
147
|
+
const cls = renderGuide(false);
|
|
148
|
+
const anchor = styleAnchor(cls);
|
|
149
|
+
// This specific guide's class must not anchor any ::before foot rule.
|
|
150
|
+
expect(sheetText()).not.toMatch(new RegExp(`${anchor}\\s*~[^{]*::before`));
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('stops the vertical segment at the centre of the last row (└)', () => {
|
|
154
|
+
const cls = renderGuide(true);
|
|
155
|
+
const anchor = styleAnchor(cls);
|
|
156
|
+
const css = sheetText();
|
|
157
|
+
|
|
158
|
+
// Middle rows: full-height vertical (├) on the row itself.
|
|
159
|
+
const middleRule =
|
|
160
|
+
css.match(
|
|
161
|
+
new RegExp(
|
|
162
|
+
`${anchor}\\s*~\\s*\\[data-part="item"\\]:not\\(:last-child\\)::after\\s*\\{[^}]*\\}`
|
|
163
|
+
)
|
|
164
|
+
)?.[0] ?? '';
|
|
165
|
+
expect(middleRule).toContain('inset-block-start:0');
|
|
166
|
+
expect(middleRule).toContain('inset-block-end:0');
|
|
167
|
+
expect(middleRule).toContain('width:1px');
|
|
168
|
+
|
|
169
|
+
// Last leaf row: segment ends at 50% so the foot forms a └.
|
|
170
|
+
const lastItemRule =
|
|
171
|
+
css.match(
|
|
172
|
+
new RegExp(
|
|
173
|
+
`${anchor}\\s*~\\s*\\[data-part="item"\\]:last-child::after\\s*\\{[^}]*\\}`
|
|
174
|
+
)
|
|
175
|
+
)?.[0] ?? '';
|
|
176
|
+
expect(lastItemRule).toContain('inset-block-start:0');
|
|
177
|
+
expect(lastItemRule).toContain('height:50%');
|
|
178
|
+
|
|
179
|
+
// Last branch row: half-height segment on its control only, so an
|
|
180
|
+
// expanded last branch has no rail running past its elbow.
|
|
181
|
+
const lastBranchRule =
|
|
182
|
+
css.match(
|
|
183
|
+
new RegExp(
|
|
184
|
+
`${anchor}\\s*~\\s*\\[data-part="branch"\\]:last-child\\s*>\\s*\\[data-part="branch-control"\\]::after\\s*\\{[^}]*\\}`
|
|
185
|
+
)
|
|
186
|
+
)?.[0] ?? '';
|
|
187
|
+
expect(lastBranchRule).toContain('height:50%');
|
|
188
|
+
|
|
189
|
+
// Non-last branch: pass-through rail spans the whole expanded subtree,
|
|
190
|
+
// painted above row hover backgrounds like the original slot rail.
|
|
191
|
+
const passThroughRule =
|
|
192
|
+
css.match(
|
|
193
|
+
new RegExp(
|
|
194
|
+
`${anchor}\\s*~\\s*\\[data-part="branch"\\]:not\\(:last-child\\)::before\\s*\\{[^}]*\\}`
|
|
195
|
+
)
|
|
196
|
+
)?.[0] ?? '';
|
|
197
|
+
expect(passThroughRule).toContain('inset-block-end:0');
|
|
198
|
+
expect(passThroughRule).toContain('z-index:1');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('paints the guide with the custom guideColor token', () => {
|
|
202
|
+
const cls = renderGuide(true, { guideColor: 'red.500' });
|
|
203
|
+
const anchor = styleAnchor(cls);
|
|
204
|
+
const css = sheetText();
|
|
205
|
+
const footRule =
|
|
206
|
+
css.match(
|
|
207
|
+
new RegExp(
|
|
208
|
+
`${anchor}\\s*~\\s*\\[data-part="item"\\]::before\\s*\\{[^}]*\\}`
|
|
209
|
+
)
|
|
210
|
+
)?.[0] ?? '';
|
|
211
|
+
expect(footRule).toMatch(/--chakra-colors-red-500|red\.500/);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
@@ -3,18 +3,144 @@ import { TreeView as ChakraTreeView } from '@chakra-ui/react';
|
|
|
3
3
|
|
|
4
4
|
import { TreeBranchIndentGuideProps } from './Tree.types';
|
|
5
5
|
|
|
6
|
+
// Extra per-level indentation in elbow mode. Guide-line trees read better
|
|
7
|
+
// with a little more horizontal air between the elbow and the row text,
|
|
8
|
+
// so each level indents this much further than the recipe default. The
|
|
9
|
+
// foot length does NOT grow with it (its default is derived from the
|
|
10
|
+
// original `--tree-indentation`), so the foot-to-text gap widens by the
|
|
11
|
+
// same amount.
|
|
12
|
+
const ELBOW_EXTRA_INDENT = '4px';
|
|
13
|
+
|
|
14
|
+
// The widened per-level indentation, defined on the guide's sibling rows
|
|
15
|
+
// so both the recipe's offset math and our pseudo-element math can share
|
|
16
|
+
// it. Root rows (depth 1) have no guide sibling, but their offset
|
|
17
|
+
// multiplies indentation by zero, so every level stays consistent.
|
|
18
|
+
const ELBOW_INDENT_VARS = {
|
|
19
|
+
'--tree-elbow-indentation': `calc(var(--tree-indentation) + ${ELBOW_EXTRA_INDENT})`,
|
|
20
|
+
} as const;
|
|
21
|
+
|
|
22
|
+
// Re-derive the recipe's `--tree-indentation-offset` (which feeds
|
|
23
|
+
// `--tree-offset`, the row's padding-start) from the widened indentation.
|
|
24
|
+
// Must be set on the same elements the recipe defines it on
|
|
25
|
+
// (item / branch-control) — inheritance can't override those.
|
|
26
|
+
const ELBOW_OFFSET_VARS = {
|
|
27
|
+
'--tree-indentation-offset':
|
|
28
|
+
'calc(var(--tree-elbow-indentation) * var(--tree-depth))',
|
|
29
|
+
} as const;
|
|
30
|
+
|
|
31
|
+
// The parent rail column, measured from a child row (item / branch-control).
|
|
32
|
+
// A row's content starts at its own `--tree-offset`, and the parent rail
|
|
33
|
+
// sits exactly one (widened) indentation to its left (verified against the
|
|
34
|
+
// recipe's offset math for both the guide and the row slots).
|
|
35
|
+
const ROW_RAIL_COLUMN =
|
|
36
|
+
'calc(var(--tree-offset) - var(--tree-elbow-indentation))';
|
|
37
|
+
|
|
38
|
+
// The same column, computed from the `branch` element itself. A branch
|
|
39
|
+
// only carries Ark's inline `--depth` (the recipe's `--tree-offset` is
|
|
40
|
+
// defined on item/branch-control, not here), so expand the recipe math:
|
|
41
|
+
// rail(x) = padding + indentation * (depth - 2) + icon * 0.5 * (depth - 1)
|
|
42
|
+
// — algebraically identical to ROW_RAIL_COLUMN for a row at `--depth`.
|
|
43
|
+
const BRANCH_RAIL_COLUMN =
|
|
44
|
+
'calc(var(--tree-padding-inline) + var(--tree-elbow-indentation) * (var(--depth) - 2) + var(--tree-icon-size) * 0.5 * (var(--depth) - 1))';
|
|
45
|
+
|
|
6
46
|
export const TreeBranchIndentGuide = forwardRef<
|
|
7
47
|
HTMLDivElement,
|
|
8
48
|
TreeBranchIndentGuideProps
|
|
9
|
-
>(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
)
|
|
19
|
-
|
|
49
|
+
>(
|
|
50
|
+
(
|
|
51
|
+
{
|
|
52
|
+
elbow = false,
|
|
53
|
+
footLength = 'calc(var(--tree-indentation) - 4px)',
|
|
54
|
+
guideColor = 'border.default',
|
|
55
|
+
...props
|
|
56
|
+
},
|
|
57
|
+
ref
|
|
58
|
+
) => {
|
|
59
|
+
// Chakra v3's `branchIndentGuide` slot renders ONE absolutely-positioned
|
|
60
|
+
// vertical line per `branchContent` (its first child), `height: 100%`,
|
|
61
|
+
// with `insetInlineStart` auto-calculated from tree depth. Don't add
|
|
62
|
+
// `ms`/`ps` (breaks the depth math) or `borderInlineStartWidth` (stacks
|
|
63
|
+
// a second 1px stroke on the slot's own bg-painted 1px) — only `bg`.
|
|
64
|
+
//
|
|
65
|
+
// That single full-height rail cannot form a `└` on the last row: it
|
|
66
|
+
// always paints past the last row's centre down to the content bottom.
|
|
67
|
+
// So in `elbow` mode the slot's own rail is hidden (`bg: transparent`,
|
|
68
|
+
// the element stays as the CSS sibling anchor) and the guide lines are
|
|
69
|
+
// re-drawn per row on the guide's sibling rows (`[data-part="item"]`
|
|
70
|
+
// leaves and `[data-part="branch"]` subtrees):
|
|
71
|
+
//
|
|
72
|
+
// - every row gets a `::before` foot — the horizontal cross-stroke
|
|
73
|
+
// from the rail column toward the row content, pinned to the row's
|
|
74
|
+
// vertical centre;
|
|
75
|
+
// - non-last rows get a full-height vertical segment (`├`); for a
|
|
76
|
+
// branch it is drawn on the `[data-part="branch"]` element so it
|
|
77
|
+
// passes through the whole expanded subtree, like the original rail
|
|
78
|
+
// (zIndex 1 for the same paint-above-row-hover reason);
|
|
79
|
+
// - the LAST row's segment stops at the row's vertical centre where
|
|
80
|
+
// the foot meets it (`└`); for a branch that means half the
|
|
81
|
+
// branch-control only, so an expanded last branch hangs below the
|
|
82
|
+
// elbow with no rail running past it.
|
|
83
|
+
const foot = {
|
|
84
|
+
content: '""',
|
|
85
|
+
position: 'absolute',
|
|
86
|
+
insetBlockStart: '50%',
|
|
87
|
+
insetInlineStart: ROW_RAIL_COLUMN,
|
|
88
|
+
width: footLength,
|
|
89
|
+
height: '1px',
|
|
90
|
+
bg: guideColor,
|
|
91
|
+
} as const;
|
|
92
|
+
const rail = {
|
|
93
|
+
content: '""',
|
|
94
|
+
position: 'absolute',
|
|
95
|
+
insetBlockStart: '0',
|
|
96
|
+
insetInlineStart: ROW_RAIL_COLUMN,
|
|
97
|
+
width: '1px',
|
|
98
|
+
bg: guideColor,
|
|
99
|
+
} as const;
|
|
100
|
+
const elbowCss = elbow
|
|
101
|
+
? {
|
|
102
|
+
'& ~ [data-part="item"]': {
|
|
103
|
+
...ELBOW_INDENT_VARS,
|
|
104
|
+
...ELBOW_OFFSET_VARS,
|
|
105
|
+
},
|
|
106
|
+
// branch-control reads --tree-elbow-indentation by inheritance,
|
|
107
|
+
// but the offset override must sit on the control itself.
|
|
108
|
+
'& ~ [data-part="branch"]': ELBOW_INDENT_VARS,
|
|
109
|
+
'& ~ [data-part="branch"] > [data-part="branch-control"]':
|
|
110
|
+
ELBOW_OFFSET_VARS,
|
|
111
|
+
'& ~ [data-part="item"]::before': foot,
|
|
112
|
+
'& ~ [data-part="branch"] > [data-part="branch-control"]::before':
|
|
113
|
+
foot,
|
|
114
|
+
'& ~ [data-part="item"]:not(:last-child)::after': {
|
|
115
|
+
...rail,
|
|
116
|
+
insetBlockEnd: '0',
|
|
117
|
+
},
|
|
118
|
+
'& ~ [data-part="item"]:last-child::after': {
|
|
119
|
+
...rail,
|
|
120
|
+
height: '50%',
|
|
121
|
+
},
|
|
122
|
+
'& ~ [data-part="branch"]:not(:last-child)::before': {
|
|
123
|
+
...rail,
|
|
124
|
+
insetBlockEnd: '0',
|
|
125
|
+
insetInlineStart: BRANCH_RAIL_COLUMN,
|
|
126
|
+
zIndex: 1,
|
|
127
|
+
},
|
|
128
|
+
'& ~ [data-part="branch"]:last-child > [data-part="branch-control"]::after':
|
|
129
|
+
{
|
|
130
|
+
...rail,
|
|
131
|
+
height: '50%',
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
: undefined;
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<ChakraTreeView.BranchIndentGuide
|
|
138
|
+
ref={ref}
|
|
139
|
+
bg={elbow ? 'transparent' : guideColor}
|
|
140
|
+
css={elbowCss}
|
|
141
|
+
{...props}
|
|
142
|
+
/>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
);
|
|
20
146
|
TreeBranchIndentGuide.displayName = 'TreeBranchIndentGuide';
|
|
@@ -23,7 +23,11 @@ export const Link = forwardRef<HTMLAnchorElement, LinkCustomProps>(
|
|
|
23
23
|
return (
|
|
24
24
|
<ChakraLink
|
|
25
25
|
ref={ref}
|
|
26
|
-
|
|
26
|
+
// Inherit the surrounding text weight so an inline link reads as part of
|
|
27
|
+
// its sentence (extrabold inside an H1, body weight inside a paragraph)
|
|
28
|
+
// rather than a fixed-semibold patch. Color + underline carry the link
|
|
29
|
+
// affordance. Override with an explicit `fontWeight` when needed.
|
|
30
|
+
fontWeight="inherit"
|
|
27
31
|
wordBreak="keep-all"
|
|
28
32
|
color={linkColor}
|
|
29
33
|
_hover={{
|
package/src/index.ts
CHANGED
|
@@ -53,7 +53,6 @@ export * from './components/LogicianProvider';
|
|
|
53
53
|
export * from './components/Markdown';
|
|
54
54
|
export * from './components/Masonry';
|
|
55
55
|
export * from './components/MaxLengthIndicator';
|
|
56
|
-
export * from './components/MDXEditor';
|
|
57
56
|
export * from './components/Menu';
|
|
58
57
|
export * from './components/Modal';
|
|
59
58
|
export * from './components/MonthPicker';
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { Box, Grid, HStack, Stack, VStack } from '@chakra-ui/react';
|
|
2
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
|
|
5
|
+
import { InlineCode } from '../components/InlineCode';
|
|
6
|
+
import { SegmentedControl } from '../components/SegmentedControl';
|
|
7
|
+
import {
|
|
8
|
+
ProgressSegment,
|
|
9
|
+
SegmentedProgressBar,
|
|
10
|
+
} from '../components/SegmentedProgressBar';
|
|
11
|
+
import {
|
|
12
|
+
Slider,
|
|
13
|
+
SliderControl,
|
|
14
|
+
SliderFilledTrack,
|
|
15
|
+
SliderThumb,
|
|
16
|
+
SliderTrack,
|
|
17
|
+
} from '../components/Slider';
|
|
18
|
+
import { Spinner } from '../components/Spinner';
|
|
19
|
+
import { Switch } from '../components/Switch';
|
|
20
|
+
import { H4, Subtext, Text } from '../components/Typography';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* # Surfaces & contrast
|
|
24
|
+
*
|
|
25
|
+
* Page-like compositions for eyeballing whether components keep enough contrast
|
|
26
|
+
* on each of the three main page-background tokens. Flip the **Color mode**
|
|
27
|
+
* toolbar control to check both light and dark.
|
|
28
|
+
*
|
|
29
|
+
* The page tokens resolve to:
|
|
30
|
+
* | token | light | role |
|
|
31
|
+
* | --- | --- | --- |
|
|
32
|
+
* | `bg.surface` | `white` | cards / elevated surfaces |
|
|
33
|
+
* | `bg.sunken` | `gray.50` | primary page wash for list / overview pages |
|
|
34
|
+
* | `bg.canvas` | `gray.0` | navigation chrome |
|
|
35
|
+
*
|
|
36
|
+
* The trap: several component fills also live in the lightest grays — `bg.subtle`
|
|
37
|
+
* is `gray.50` (**identical to `bg.sunken`**) and `bg.muted` is `gray.100`
|
|
38
|
+
* (~1.03:1 against `bg.sunken`). A component that paints a resting surface with
|
|
39
|
+
* those tokens reads fine on a `bg.surface` card but loses its bounds on a
|
|
40
|
+
* `bg.sunken` page. These stories exist to catch exactly that — a control should
|
|
41
|
+
* still read as a bounded control on all three surfaces, in both modes.
|
|
42
|
+
*/
|
|
43
|
+
const meta = {
|
|
44
|
+
title: 'Theme/Surfaces',
|
|
45
|
+
parameters: {
|
|
46
|
+
layout: 'fullscreen',
|
|
47
|
+
},
|
|
48
|
+
} satisfies Meta;
|
|
49
|
+
|
|
50
|
+
export default meta;
|
|
51
|
+
|
|
52
|
+
type Story = StoryObj;
|
|
53
|
+
|
|
54
|
+
const SEGMENT_OPTIONS = [
|
|
55
|
+
{ label: 'Day', value: 'day' },
|
|
56
|
+
{ label: 'Week', value: 'week' },
|
|
57
|
+
{ label: 'Month', value: 'month' },
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
const PAGE_SURFACES = ['bg.surface', 'bg.sunken', 'bg.canvas'] as const;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The full set of at-risk components rendered once per page-background token.
|
|
64
|
+
* Scan each column top to bottom: every control should have a visible edge or
|
|
65
|
+
* fill on every surface. Anything that melts into the background on `bg.sunken`
|
|
66
|
+
* (the middle column in light mode) is the bug this story is here to surface.
|
|
67
|
+
*/
|
|
68
|
+
const SurfaceShowcase = () => {
|
|
69
|
+
const [segment, setSegment] = useState('week');
|
|
70
|
+
const [sliderValue, setSliderValue] = useState([40]);
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<Grid templateColumns={`repeat(${PAGE_SURFACES.length}, 1fr)`} gap={0}>
|
|
74
|
+
{PAGE_SURFACES.map((surface) => (
|
|
75
|
+
<VStack
|
|
76
|
+
key={surface}
|
|
77
|
+
bg={surface}
|
|
78
|
+
p={8}
|
|
79
|
+
gap={6}
|
|
80
|
+
align="stretch"
|
|
81
|
+
minH="100vh"
|
|
82
|
+
>
|
|
83
|
+
<Box>
|
|
84
|
+
<H4>on {surface}</H4>
|
|
85
|
+
<Subtext>
|
|
86
|
+
{surface === 'bg.sunken'
|
|
87
|
+
? 'the page-wash case where bg.subtle / bg.muted fills collide'
|
|
88
|
+
: surface === 'bg.surface'
|
|
89
|
+
? 'the elevated-card baseline'
|
|
90
|
+
: 'navigation-chrome surface'}
|
|
91
|
+
</Subtext>
|
|
92
|
+
</Box>
|
|
93
|
+
|
|
94
|
+
<VStack align="stretch" gap={2}>
|
|
95
|
+
<Subtext>SegmentedControl (outline: border.default)</Subtext>
|
|
96
|
+
<SegmentedControl
|
|
97
|
+
options={SEGMENT_OPTIONS}
|
|
98
|
+
value={segment}
|
|
99
|
+
onSelect={setSegment}
|
|
100
|
+
size="sm"
|
|
101
|
+
/>
|
|
102
|
+
</VStack>
|
|
103
|
+
|
|
104
|
+
<VStack align="stretch" gap={2}>
|
|
105
|
+
<Subtext>InlineCode (outline: border.default)</Subtext>
|
|
106
|
+
<Text>
|
|
107
|
+
Run <InlineCode>yarn build</InlineCode> to compile.
|
|
108
|
+
</Text>
|
|
109
|
+
</VStack>
|
|
110
|
+
|
|
111
|
+
<VStack align="stretch" gap={2}>
|
|
112
|
+
<Subtext>Switch — off-state track (fill: bg.track)</Subtext>
|
|
113
|
+
<HStack gap={4}>
|
|
114
|
+
<Switch size="md">
|
|
115
|
+
<Switch.Control />
|
|
116
|
+
</Switch>
|
|
117
|
+
<Switch size="md" defaultChecked>
|
|
118
|
+
<Switch.Control />
|
|
119
|
+
</Switch>
|
|
120
|
+
</HStack>
|
|
121
|
+
</VStack>
|
|
122
|
+
|
|
123
|
+
<VStack align="stretch" gap={2}>
|
|
124
|
+
<Subtext>Slider — empty rail (fill: bg.track)</Subtext>
|
|
125
|
+
<Slider
|
|
126
|
+
value={sliderValue}
|
|
127
|
+
min={0}
|
|
128
|
+
max={100}
|
|
129
|
+
onValueChange={(details) => setSliderValue(details.value)}
|
|
130
|
+
>
|
|
131
|
+
<SliderControl>
|
|
132
|
+
<SliderTrack>
|
|
133
|
+
<SliderFilledTrack />
|
|
134
|
+
</SliderTrack>
|
|
135
|
+
<SliderThumb index={0} />
|
|
136
|
+
</SliderControl>
|
|
137
|
+
</Slider>
|
|
138
|
+
</VStack>
|
|
139
|
+
|
|
140
|
+
<VStack align="stretch" gap={2}>
|
|
141
|
+
<Subtext>SegmentedProgressBar — empty portion (fill: bg.track)</Subtext>
|
|
142
|
+
<SegmentedProgressBar max={100}>
|
|
143
|
+
<ProgressSegment value={45} filledTrackColor="primary.main" />
|
|
144
|
+
</SegmentedProgressBar>
|
|
145
|
+
</VStack>
|
|
146
|
+
|
|
147
|
+
<VStack align="start" gap={2}>
|
|
148
|
+
<Subtext>Spinner — track (bg.track)</Subtext>
|
|
149
|
+
<Spinner />
|
|
150
|
+
</VStack>
|
|
151
|
+
</VStack>
|
|
152
|
+
))}
|
|
153
|
+
</Grid>
|
|
154
|
+
);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Side-by-side reference: all at-risk components on `bg.surface`, `bg.sunken`,
|
|
159
|
+
* and `bg.canvas` at once. In light mode, watch the middle (`bg.sunken`) column
|
|
160
|
+
* — every control should still read as a bounded control there. Structured
|
|
161
|
+
* controls (`SegmentedControl`, `InlineCode`) are outlined (`border.default`);
|
|
162
|
+
* meter surfaces (`Switch`, `Slider`, `SegmentedProgressBar`,
|
|
163
|
+
* `Spinner`) use the `bg.track` fill so the track reads on the page wash.
|
|
164
|
+
*/
|
|
165
|
+
export const ContrastOnPageBackgrounds: Story = {
|
|
166
|
+
render: () => <SurfaceShowcase />,
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* A realistic `bg.sunken` overview page (the most common offender surface): a
|
|
171
|
+
* filter bar of `SegmentedControl`s and inline-code chips sitting directly on
|
|
172
|
+
* the page wash, with no intervening card. This is what an end user actually
|
|
173
|
+
* sees on a list/index page.
|
|
174
|
+
*/
|
|
175
|
+
export const SunkenOverviewPage: Story = {
|
|
176
|
+
render: () => {
|
|
177
|
+
const [range, setRange] = useState('week');
|
|
178
|
+
const [view, setView] = useState('list');
|
|
179
|
+
return (
|
|
180
|
+
<Box bg="bg.sunken" minH="100vh" p={8}>
|
|
181
|
+
<Stack gap={6} maxW="720px">
|
|
182
|
+
<H4>Usage overview</H4>
|
|
183
|
+
<HStack gap={4} wrap="wrap">
|
|
184
|
+
<SegmentedControl
|
|
185
|
+
options={SEGMENT_OPTIONS}
|
|
186
|
+
value={range}
|
|
187
|
+
onSelect={setRange}
|
|
188
|
+
size="sm"
|
|
189
|
+
/>
|
|
190
|
+
<SegmentedControl
|
|
191
|
+
options={[
|
|
192
|
+
{ label: 'List', value: 'list' },
|
|
193
|
+
{ label: 'Grid', value: 'grid' },
|
|
194
|
+
]}
|
|
195
|
+
value={view}
|
|
196
|
+
onSelect={setView}
|
|
197
|
+
size="sm"
|
|
198
|
+
/>
|
|
199
|
+
</HStack>
|
|
200
|
+
<Text>
|
|
201
|
+
Filtered by <InlineCode>status:active</InlineCode> over the selected
|
|
202
|
+
range.
|
|
203
|
+
</Text>
|
|
204
|
+
</Stack>
|
|
205
|
+
</Box>
|
|
206
|
+
);
|
|
207
|
+
},
|
|
208
|
+
};
|
package/src/theme/colors.ts
CHANGED
|
@@ -403,6 +403,18 @@ export const semanticTokens = {
|
|
|
403
403
|
sunken: {
|
|
404
404
|
value: { base: '{colors.gray.50}', _dark: desaturatedGray[1500] },
|
|
405
405
|
},
|
|
406
|
+
// Recessed control-track surface for "meter"-style components whose
|
|
407
|
+
// unfilled track carries meaning (Switch off-state, Slider rail,
|
|
408
|
+
// SegmentedProgressBar remainder, Spinner track). Unlike `bg.subtle` /
|
|
409
|
+
// `bg.muted` (gray.50 / gray.100), which collapse into the `bg.sunken`
|
|
410
|
+
// page wash in light mode, this sits at gray.300 so the track reads as a
|
|
411
|
+
// filled surface on any background. Dark keeps the `bg.muted` value
|
|
412
|
+
// (desaturatedGray[1200]) — already lighter than the dark canvas/surface,
|
|
413
|
+
// so it reads there without change. Use this only for control tracks, not
|
|
414
|
+
// as a general fill (that stays `bg.subtle` / `bg.muted`).
|
|
415
|
+
track: {
|
|
416
|
+
value: { base: '{colors.gray.300}', _dark: desaturatedGray[1200] },
|
|
417
|
+
},
|
|
406
418
|
// Override Chakra's default `bg.panel` (whose `_dark` resolves to Chakra's
|
|
407
419
|
// own gray.950 = #111111, off our slate palette). Light value is white —
|
|
408
420
|
// identical to Chakra's default — so this only realigns dark overlay
|
|
@@ -530,6 +542,7 @@ export type SemanticColorToken =
|
|
|
530
542
|
| 'subtle'
|
|
531
543
|
| 'muted'
|
|
532
544
|
| 'sunken'
|
|
545
|
+
| 'track'
|
|
533
546
|
| 'inverse'
|
|
534
547
|
| 'selected'
|
|
535
548
|
| 'highlighted'}`
|
package/src/theme/global.ts
CHANGED
|
@@ -26,7 +26,10 @@ export const globalCss = defineGlobalStyles({
|
|
|
26
26
|
|
|
27
27
|
html: {
|
|
28
28
|
height: 'var(--chakra-vh)',
|
|
29
|
-
|
|
29
|
+
// Global base font size. The whole type/spacing scale is em-relative, so
|
|
30
|
+
// this single value drives the app-wide baseline; surfaces that need a
|
|
31
|
+
// different base (e.g. FactChat chat at 16px) opt in via <ScaledContext>.
|
|
32
|
+
fontSize: 14,
|
|
30
33
|
fontFamily: [
|
|
31
34
|
pretendard.style.fontFamily,
|
|
32
35
|
inter.style.fontFamily,
|
package/src/theme/index.ts
CHANGED
|
@@ -18,21 +18,25 @@ const SHARED_TEXT_STYLE_VALUES = {
|
|
|
18
18
|
h1: {
|
|
19
19
|
fontFamily: 'heading',
|
|
20
20
|
fontSize: { base: '2.4em', md: '3em' }, // 38.4px → 48px
|
|
21
|
-
|
|
21
|
+
// Display sizes carry the heaviest weight; the tight negative tracking
|
|
22
|
+
// above keeps extrabold from reading loose at 48px.
|
|
23
|
+
fontWeight: 'extrabold',
|
|
22
24
|
lineHeight: '1.33',
|
|
23
25
|
letterSpacing: '-0.02em',
|
|
24
26
|
},
|
|
25
27
|
h2: {
|
|
26
28
|
fontFamily: 'heading',
|
|
27
29
|
fontSize: { base: '2em', md: '2.5em' }, // 32px → 40px
|
|
28
|
-
fontWeight: '
|
|
30
|
+
fontWeight: 'extrabold',
|
|
29
31
|
lineHeight: '1.33',
|
|
30
32
|
letterSpacing: '-0.01em',
|
|
31
33
|
},
|
|
32
34
|
h3: {
|
|
33
35
|
fontFamily: 'heading',
|
|
34
36
|
fontSize: { base: '1.5em', md: '1.75em' }, // 24px → 28px
|
|
35
|
-
|
|
37
|
+
// Steps down to bold — the weight ramp tapers as size shrinks so heavy
|
|
38
|
+
// display weights don't muddy the smaller headings (h4/h5 stay lighter).
|
|
39
|
+
fontWeight: 'bold',
|
|
36
40
|
lineHeight: '1.33',
|
|
37
41
|
letterSpacing: '-0.01em',
|
|
38
42
|
},
|
|
@@ -74,7 +78,7 @@ const SHARED_TEXT_STYLE_VALUES = {
|
|
|
74
78
|
* Text styles for consistent typography across the application
|
|
75
79
|
* Names match fontSize tokens for easy migration: fontStyle="h1" → textStyle="h1"
|
|
76
80
|
*
|
|
77
|
-
* Base font size:
|
|
81
|
+
* Base font size: 14px (set in global.ts)
|
|
78
82
|
* Responsive scaling using em units (relative to nearest ancestor font-size):
|
|
79
83
|
* - Mobile (base): Slightly smaller for better readability on small screens
|
|
80
84
|
* - Desktop (md+): Original design sizes
|