@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.
Files changed (125) hide show
  1. package/dist/components/InlineCode/InlineCode.d.ts.map +1 -1
  2. package/dist/components/InlineCode/InlineCode.js +8 -1
  3. package/dist/components/InlineCode/InlineCode.js.map +1 -1
  4. package/dist/components/InlineCode/InlineCode.mjs +8 -1
  5. package/dist/components/InlineCode/InlineCode.mjs.map +1 -1
  6. package/dist/components/Loaders/PageLoader.d.ts.map +1 -1
  7. package/dist/components/Loaders/PageLoader.js +2 -2
  8. package/dist/components/Loaders/PageLoader.js.map +1 -1
  9. package/dist/components/Loaders/PageLoader.mjs +2 -2
  10. package/dist/components/Loaders/PageLoader.mjs.map +1 -1
  11. package/dist/components/Loaders/SectionLoader.d.ts.map +1 -1
  12. package/dist/components/Loaders/SectionLoader.js +2 -2
  13. package/dist/components/Loaders/SectionLoader.js.map +1 -1
  14. package/dist/components/Loaders/SectionLoader.mjs +2 -2
  15. package/dist/components/Loaders/SectionLoader.mjs.map +1 -1
  16. package/dist/components/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  17. package/dist/components/SegmentedControl/SegmentedControl.js +23 -15
  18. package/dist/components/SegmentedControl/SegmentedControl.js.map +1 -1
  19. package/dist/components/SegmentedControl/SegmentedControl.mjs +23 -15
  20. package/dist/components/SegmentedControl/SegmentedControl.mjs.map +1 -1
  21. package/dist/components/SegmentedProgressBar/SegmentedProgressBar.d.ts.map +1 -1
  22. package/dist/components/SegmentedProgressBar/SegmentedProgressBar.js +6 -1
  23. package/dist/components/SegmentedProgressBar/SegmentedProgressBar.js.map +1 -1
  24. package/dist/components/SegmentedProgressBar/SegmentedProgressBar.mjs +6 -1
  25. package/dist/components/SegmentedProgressBar/SegmentedProgressBar.mjs.map +1 -1
  26. package/dist/components/Slider/SliderTrack/SliderTrack.d.ts.map +1 -1
  27. package/dist/components/Slider/SliderTrack/SliderTrack.js +13 -1
  28. package/dist/components/Slider/SliderTrack/SliderTrack.js.map +1 -1
  29. package/dist/components/Slider/SliderTrack/SliderTrack.mjs +13 -1
  30. package/dist/components/Slider/SliderTrack/SliderTrack.mjs.map +1 -1
  31. package/dist/components/Spinner/Spinner.d.ts.map +1 -1
  32. package/dist/components/Spinner/Spinner.js +7 -1
  33. package/dist/components/Spinner/Spinner.js.map +1 -1
  34. package/dist/components/Spinner/Spinner.mjs +7 -1
  35. package/dist/components/Spinner/Spinner.mjs.map +1 -1
  36. package/dist/components/Switch/SwitchControl.d.ts.map +1 -1
  37. package/dist/components/Switch/SwitchControl.js +7 -1
  38. package/dist/components/Switch/SwitchControl.js.map +1 -1
  39. package/dist/components/Switch/SwitchControl.mjs +7 -1
  40. package/dist/components/Switch/SwitchControl.mjs.map +1 -1
  41. package/dist/components/Tag/Tag.d.ts.map +1 -1
  42. package/dist/components/Tag/Tag.js +1 -1
  43. package/dist/components/Tag/Tag.js.map +1 -1
  44. package/dist/components/Tag/Tag.mjs +1 -1
  45. package/dist/components/Tag/Tag.mjs.map +1 -1
  46. package/dist/components/Tag/Tag.styles.d.ts.map +1 -1
  47. package/dist/components/Tag/Tag.styles.js +22 -16
  48. package/dist/components/Tag/Tag.styles.js.map +1 -1
  49. package/dist/components/Tag/Tag.styles.mjs +22 -16
  50. package/dist/components/Tag/Tag.styles.mjs.map +1 -1
  51. package/dist/components/Tree/Tree.d.ts +5 -1
  52. package/dist/components/Tree/Tree.d.ts.map +1 -1
  53. package/dist/components/Tree/Tree.types.d.ts +29 -1
  54. package/dist/components/Tree/Tree.types.d.ts.map +1 -1
  55. package/dist/components/Tree/TreeBranchIndentGuide.d.ts +5 -1
  56. package/dist/components/Tree/TreeBranchIndentGuide.d.ts.map +1 -1
  57. package/dist/components/Tree/TreeBranchIndentGuide.js +107 -8
  58. package/dist/components/Tree/TreeBranchIndentGuide.js.map +1 -1
  59. package/dist/components/Tree/TreeBranchIndentGuide.mjs +107 -8
  60. package/dist/components/Tree/TreeBranchIndentGuide.mjs.map +1 -1
  61. package/dist/components/Typography/Link.d.ts.map +1 -1
  62. package/dist/components/Typography/Link.js +6 -1
  63. package/dist/components/Typography/Link.js.map +1 -1
  64. package/dist/components/Typography/Link.mjs +6 -1
  65. package/dist/components/Typography/Link.mjs.map +1 -1
  66. package/dist/index.d.ts +0 -1
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +0 -2
  69. package/dist/index.js.map +1 -1
  70. package/dist/index.mjs +0 -1
  71. package/dist/index.mjs.map +1 -1
  72. package/dist/theme/colors.d.ts +7 -1
  73. package/dist/theme/colors.d.ts.map +1 -1
  74. package/dist/theme/colors.js +12 -0
  75. package/dist/theme/colors.js.map +1 -1
  76. package/dist/theme/colors.mjs +12 -0
  77. package/dist/theme/colors.mjs.map +1 -1
  78. package/dist/theme/global.d.ts.map +1 -1
  79. package/dist/theme/global.js +4 -1
  80. package/dist/theme/global.js.map +1 -1
  81. package/dist/theme/global.mjs +4 -1
  82. package/dist/theme/global.mjs.map +1 -1
  83. package/dist/theme/index.d.ts +1 -1
  84. package/dist/theme/index.d.ts.map +1 -1
  85. package/dist/theme/index.js +8 -4
  86. package/dist/theme/index.js.map +1 -1
  87. package/dist/theme/index.mjs +8 -4
  88. package/dist/theme/index.mjs.map +1 -1
  89. package/package.json +1 -3
  90. package/src/components/InlineCode/InlineCode.tsx +7 -0
  91. package/src/components/Loaders/PageLoader.tsx +3 -5
  92. package/src/components/Loaders/SectionLoader.tsx +3 -2
  93. package/src/components/SegmentedControl/SegmentedControl.stories.tsx +27 -1
  94. package/src/components/SegmentedControl/SegmentedControl.tsx +22 -15
  95. package/src/components/SegmentedProgressBar/SegmentedProgressBar.tsx +5 -1
  96. package/src/components/Slider/SliderTrack/SliderTrack.tsx +10 -1
  97. package/src/components/Spinner/Spinner.tsx +6 -1
  98. package/src/components/Switch/SwitchControl.tsx +6 -1
  99. package/src/components/Tag/Tag.styles.ts +22 -16
  100. package/src/components/Tag/Tag.tsx +1 -0
  101. package/src/components/Tree/Tree.GuideElbow.stories.tsx +220 -0
  102. package/src/components/Tree/Tree.types.ts +29 -1
  103. package/src/components/Tree/TreeBranchIndentGuide.test.tsx +213 -0
  104. package/src/components/Tree/TreeBranchIndentGuide.tsx +137 -11
  105. package/src/components/Typography/Link.tsx +5 -1
  106. package/src/index.ts +0 -1
  107. package/src/theme/Surfaces.stories.tsx +208 -0
  108. package/src/theme/colors.ts +13 -0
  109. package/src/theme/global.ts +4 -1
  110. package/src/theme/index.ts +8 -4
  111. package/dist/components/MDXEditor/MDXEditor.d.ts +0 -6
  112. package/dist/components/MDXEditor/MDXEditor.d.ts.map +0 -1
  113. package/dist/components/MDXEditor/MDXEditor.js +0 -237
  114. package/dist/components/MDXEditor/MDXEditor.js.map +0 -1
  115. package/dist/components/MDXEditor/MDXEditor.mjs +0 -235
  116. package/dist/components/MDXEditor/MDXEditor.mjs.map +0 -1
  117. package/dist/components/MDXEditor/MDXEditor.types.d.ts +0 -7
  118. package/dist/components/MDXEditor/MDXEditor.types.d.ts.map +0 -1
  119. package/dist/components/MDXEditor/index.d.ts +0 -3
  120. package/dist/components/MDXEditor/index.d.ts.map +0 -1
  121. package/src/components/MDXEditor/MDXEditor.css +0 -40
  122. package/src/components/MDXEditor/MDXEditor.stories.tsx +0 -56
  123. package/src/components/MDXEditor/MDXEditor.tsx +0 -337
  124. package/src/components/MDXEditor/MDXEditor.types.ts +0 -7
  125. package/src/components/MDXEditor/index.ts +0 -2
@@ -1,4 +1,4 @@
1
- import { HStack, Icon, Text } from '@chakra-ui/react';
1
+ import { HStack, Icon, Stack, Text, VStack } from '@chakra-ui/react';
2
2
  import { Meta, StoryObj } from '@storybook/react';
3
3
  import React, { useState } from 'react';
4
4
  import { FiHome, FiSettings, FiUser } from 'react-icons/fi';
@@ -143,6 +143,32 @@ export const EditableLabel: StoryObj<
143
143
  ),
144
144
  };
145
145
 
146
+ /**
147
+ * The same control rendered on each of the three main page-background tokens.
148
+ * The track fill (`bg.subtle`) matches `bg.sunken` in light mode, so without a
149
+ * defining ring the control would disappear on a sunken list/overview page.
150
+ * Confirm the control reads as a bounded control on all three surfaces, in both
151
+ * light and dark (flip the **Color mode** toolbar control).
152
+ */
153
+ export const OnPageBackgrounds: Story = {
154
+ parameters: { layout: 'fullscreen' },
155
+ render: (props) => {
156
+ const surfaces = ['bg.surface', 'bg.sunken', 'bg.canvas'] as const;
157
+ return (
158
+ <Stack gap={0}>
159
+ {surfaces.map((surface) => (
160
+ <VStack key={surface} bg={surface} p={8} align="start" gap={3}>
161
+ <Text fontSize="sm" fontWeight="600" color="fg.muted">
162
+ on {surface}
163
+ </Text>
164
+ <SegmentedControl {...props} />
165
+ </VStack>
166
+ ))}
167
+ </Stack>
168
+ );
169
+ },
170
+ };
171
+
146
172
  export const WithCustomContent = {
147
173
  args: {
148
174
  options: [
@@ -66,31 +66,38 @@ export const SegmentedControl = forwardRef<
66
66
  }
67
67
  }}
68
68
  size={size}
69
- // Light mode: track is bg.subtle (gray.50) and the selected indicator is
70
- // bg.surface (white) lifted by an md shadow a classic raised thumb.
69
+ // Track is bg.subtle (gray.50 light) and the selected indicator rides on
70
+ // `bg.raised` a raised neutral surface that resolves white in light and
71
+ // the lightest neutral (gray.1100) in dark, so the thumb reads as lifted
72
+ // above the track in both modes (`bg.surface` could not: its dark value is
73
+ // darker than the track). The indicator also gains a shadow: an md drop
74
+ // shadow in light, and in dark — where a drop shadow is invisible on a
75
+ // dark canvas — a soft ambient shadow + a translucent-white hairline edge.
71
76
  //
72
- // Selected indicator rides on `bg.raised` a raised neutral surface that
73
- // resolves white in light and the lightest neutral (gray.1100) in dark,
74
- // so the thumb reads as lifted above the `bg.subtle` track in both modes
75
- // (`bg.surface` could not: its dark value is darker than the track). Dark
76
- // mode also needs two things light mode doesn't, and neither
77
- // maps to a token: a drop shadow is invisible on a dark canvas, so the
78
- // indicator gains a soft ambient shadow + a translucent-white hairline
79
- // edge; and the track only contrasts against the app canvas on a
80
- // same-or-lighter dark surface (e.g. a bg.surface card) it would vanish
81
- // and leave the control looking like floating text, so a translucent
82
- // hairline ring defines its bounds on any dark background. Both rings are
83
- // box-shadows (not borders) so the matched item-height math is preserved.
77
+ // The track itself only contrasts where the page wash is darker (light) or
78
+ // lighter (dark) than the fill. It vanishes when the page matches the
79
+ // track: a `bg.sunken` list/overview page is gray.50 in light — identical
80
+ // to the bg.subtle track and a `bg.surface` card is lighter than the
81
+ // track in dark. In either case the control would read as floating text,
82
+ // so a hairline ring defines its bounds on *any* background: a neutral
83
+ // `border.default` ring in light, a translucent-white ring in dark. (The
84
+ // ring not the track fill does the work here: the selected thumb is a
85
+ // raised white surface lifted by its shadow, a relationship that depends
86
+ // on the track staying light, so we outline the group rather than darken
87
+ // it. `border.default` keeps the edge present but light — `border.strong`
88
+ // reads as a heavy box for a control that normally has no outline.) Both
89
+ // rings are box-shadows (not borders) so the matched item-height math is
90
+ // preserved.
84
91
  bg="bg.subtle"
85
92
  p="1"
86
93
  borderRadius={borderRadius}
87
- boxShadow="none"
88
94
  w="fit-content"
89
95
  {...rest}
90
96
  css={mergeCss(
91
97
  {
92
98
  '--segment-indicator-bg': `var(--chakra-colors-bg-raised)`,
93
99
  '--segment-indicator-shadow': `var(--chakra-shadows-md)`,
100
+ boxShadow: '0 0 0 1px var(--chakra-colors-border-default)',
94
101
  '.dark &': {
95
102
  '--segment-indicator-shadow':
96
103
  '0 1px 2px rgba(0, 0, 0, 0.45), 0 0 0 1px rgba(255, 255, 255, 0.08)',
@@ -13,7 +13,11 @@ export const SegmentedProgressBar = ({
13
13
  position="relative"
14
14
  flex={1}
15
15
  display="flex"
16
- bgColor="bg.muted"
16
+ // The remainder behind the segments is a meter surface, so it needs a
17
+ // visible fill. bg.muted (gray.100) is ~1.03:1 against the bg.sunken page
18
+ // wash (gray.50) and vanishes there; bg.track (gray.300) reads as a
19
+ // filled track on any background. Filled segments paint over it.
20
+ bgColor="bg.track"
17
21
  borderRadius="full"
18
22
  w="100%"
19
23
  h="16px"
@@ -9,7 +9,16 @@ export const SliderTrack = forwardRef(
9
9
  ref?: ForwardedRef<HTMLDivElement>
10
10
  ) => {
11
11
  return (
12
- <Slider.Track bg="bg.muted" {...rest} ref={ref}>
12
+ <Slider.Track
13
+ // The empty rail is a meter surface — too thin for an outline to read as
14
+ // anything but a hollow edge — so the fill itself must contrast. bg.muted
15
+ // (gray.100) is ~1.03:1 against the bg.sunken page wash (gray.50) and
16
+ // disappears there; bg.track (gray.300) reads as a filled rail on any
17
+ // background. The filled portion stays primary (SliderFilledTrack).
18
+ bg="bg.track"
19
+ {...rest}
20
+ ref={ref}
21
+ >
13
22
  {children}
14
23
  </Slider.Track>
15
24
  );
@@ -13,7 +13,12 @@ export const Spinner = forwardRef(
13
13
  animationDuration="0.65s"
14
14
  ref={ref}
15
15
  {...rest}
16
- css={mergeCss({ '--spinner-track-color': 'colors.bg.muted' }, css)}
16
+ // The track ring is the only non-animated affordance and can't take a
17
+ // box outline (SVG-style track), so its color carries the contrast.
18
+ // bg.muted (gray.100) is ~1.03:1 against the bg.sunken page wash
19
+ // (gray.50) and vanishes there; bg.track (gray.300) reads on any
20
+ // background and stays mode-aware.
21
+ css={mergeCss({ '--spinner-track-color': 'colors.bg.track' }, css)}
17
22
  />
18
23
  );
19
24
  }
@@ -7,7 +7,12 @@ export const SwitchControl = forwardRef<HTMLSpanElement, SwitchControlProps>(
7
7
  (props, ref) => (
8
8
  <ChakraSwitch.Control
9
9
  ref={ref}
10
- bg="bg.muted"
10
+ // The off-state track is a meter surface — its extent is the information —
11
+ // so it needs a visible fill, not just an edge. bg.muted (gray.100) is
12
+ // only ~1.03:1 against the bg.sunken page wash (gray.50) and vanishes
13
+ // there; bg.track (gray.300) reads as a filled track on any background.
14
+ // The checked state is the self-defining primary.main fill.
15
+ bg="bg.track"
11
16
  _checked={{ bg: 'primary.main' }}
12
17
  {...focusRing}
13
18
  {...props}
@@ -17,7 +17,7 @@ export const tagColorPaletteStyles: Record<
17
17
  soft: {
18
18
  bgColor: 'primary.extralight',
19
19
  borderColor: 'primary.light',
20
- color: 'primary.dark',
20
+ color: 'primary.darker',
21
21
  },
22
22
  solid: {
23
23
  bgColor: 'blue.500',
@@ -27,14 +27,14 @@ export const tagColorPaletteStyles: Record<
27
27
  outline: {
28
28
  bgColor: 'transparent',
29
29
  borderColor: 'primary.main',
30
- color: 'primary.main',
30
+ color: 'primary.dark',
31
31
  },
32
32
  },
33
33
  secondary: {
34
34
  soft: {
35
35
  bgColor: 'secondary.extralight',
36
36
  borderColor: 'secondary.light',
37
- color: 'secondary.dark',
37
+ color: 'secondary.darker',
38
38
  },
39
39
  solid: {
40
40
  bgColor: 'violet.500',
@@ -44,14 +44,14 @@ export const tagColorPaletteStyles: Record<
44
44
  outline: {
45
45
  bgColor: 'transparent',
46
46
  borderColor: 'secondary.main',
47
- color: 'secondary.main',
47
+ color: 'secondary.dark',
48
48
  },
49
49
  },
50
50
  danger: {
51
51
  soft: {
52
52
  bgColor: 'danger.extralight',
53
53
  borderColor: 'danger.light',
54
- color: 'danger.dark',
54
+ color: 'danger.darker',
55
55
  },
56
56
  solid: {
57
57
  bgColor: 'rose.500',
@@ -61,14 +61,14 @@ export const tagColorPaletteStyles: Record<
61
61
  outline: {
62
62
  bgColor: 'transparent',
63
63
  borderColor: 'danger.main',
64
- color: 'danger.main',
64
+ color: 'danger.dark',
65
65
  },
66
66
  },
67
67
  success: {
68
68
  soft: {
69
69
  bgColor: 'success.extralight',
70
70
  borderColor: 'success.light',
71
- color: 'success.dark',
71
+ color: 'success.darker',
72
72
  },
73
73
  solid: {
74
74
  bgColor: 'green.600',
@@ -78,34 +78,40 @@ export const tagColorPaletteStyles: Record<
78
78
  outline: {
79
79
  bgColor: 'transparent',
80
80
  borderColor: 'success.main',
81
- color: 'success.main',
81
+ color: 'success.dark',
82
82
  },
83
83
  },
84
84
  warning: {
85
85
  soft: {
86
86
  bgColor: 'warning.extralight',
87
87
  borderColor: 'warning.light',
88
- color: 'warning.dark',
88
+ color: 'warning.darker',
89
89
  },
90
90
  solid: {
91
- bgColor: 'gold.500',
92
- borderColor: 'gold.500',
93
- color: 'gold.900', // dark text: white/gold.500 was 2.39:1; gold.900 = 6.73:1
91
+ // Deepened from gold.500 so white text clears AA and the chip matches the
92
+ // other solids (white on a saturated fill). White/gold.500 was only
93
+ // 2.39:1; white/gold.700 = 5.86:1 ✓ AA. Bright gold + near-black text read
94
+ // harsh on the dark canvas.
95
+ bgColor: 'gold.700',
96
+ borderColor: 'gold.700',
97
+ color: 'white',
94
98
  },
95
99
  outline: {
96
100
  bgColor: 'transparent',
97
101
  borderColor: 'warning.main',
98
- color: 'warning.main',
102
+ // warning.dark (gold.700, 5.8:1 AA) over warning.main (gold.500, 3.0:1) —
103
+ // darker text also lifts this off the palette's AA-risk step.
104
+ color: 'warning.dark',
99
105
  },
100
106
  },
101
107
  neutral: {
102
108
  soft: {
103
- // Flip surface + border with the mode so the fg.muted text stays legible
109
+ // Flip surface + border with the mode so the fg.default text stays legible
104
110
  // in dark. Light values preserved (bg.muted → gray.100, border.default →
105
111
  // gray.300).
106
112
  bgColor: 'bg.muted',
107
113
  borderColor: 'border.default',
108
- color: 'fg.muted',
114
+ color: 'fg.default',
109
115
  },
110
116
  solid: {
111
117
  bgColor: 'gray.800',
@@ -115,7 +121,7 @@ export const tagColorPaletteStyles: Record<
115
121
  outline: {
116
122
  bgColor: 'transparent',
117
123
  borderColor: 'border.strong', // gray.500 / gray.900 (_dark)
118
- color: 'fg.subtle',
124
+ color: 'fg.muted',
119
125
  },
120
126
  },
121
127
  };
@@ -36,6 +36,7 @@ export const Tag = forwardRef(
36
36
  borderRadius="md"
37
37
  boxShadow="none"
38
38
  fontSize="sm"
39
+ fontWeight="semibold"
39
40
  {...styles}
40
41
  {...rest}
41
42
  ref={ref}
@@ -0,0 +1,220 @@
1
+ import { Box, HStack, Stack, Text } from '@chakra-ui/react';
2
+ import { Meta, StoryObj } from '@storybook/react';
3
+
4
+ import { Tree } from './Tree';
5
+ import { createTreeCollection } from './index';
6
+
7
+ /**
8
+ * Stories for the `elbow` option on `Tree.BranchIndentGuide`.
9
+ *
10
+ * `elbow` turns the plain vertical rails into `├` / `└` guide lines:
11
+ * every row gets a horizontal foot joining it to its parent rail, and
12
+ * the last row of each group stops at its centre (`└`). `guideColor`
13
+ * customizes the line color (default `border.default`, which flips
14
+ * with color mode). Pass both through the `indentGuide` element:
15
+ *
16
+ * <Tree.Node indentGuide={<Tree.BranchIndentGuide elbow />} ... />
17
+ */
18
+
19
+ interface Node {
20
+ id: string;
21
+ name: string;
22
+ children?: Node[];
23
+ }
24
+
25
+ const orgSample: Node = {
26
+ id: 'ROOT',
27
+ name: '',
28
+ children: [
29
+ {
30
+ id: 'hq',
31
+ name: '본사',
32
+ children: [
33
+ {
34
+ id: 'hq-dev',
35
+ name: '개발본부',
36
+ children: [
37
+ {
38
+ id: 'hq-dev-backend',
39
+ name: '백엔드그룹',
40
+ children: [
41
+ { id: 'hq-dev-backend-api', name: 'API팀' },
42
+ { id: 'hq-dev-backend-db', name: 'DB팀' },
43
+ { id: 'hq-dev-backend-platform', name: '플랫폼팀' },
44
+ ],
45
+ },
46
+ {
47
+ id: 'hq-dev-frontend',
48
+ name: '프론트엔드그룹',
49
+ children: [
50
+ { id: 'hq-dev-frontend-web', name: '웹팀' },
51
+ { id: 'hq-dev-frontend-mobile', name: '모바일팀' },
52
+ ],
53
+ },
54
+ ],
55
+ },
56
+ {
57
+ id: 'hq-sales',
58
+ name: '영업본부',
59
+ children: [
60
+ { id: 'hq-sales-domestic', name: '국내영업팀' },
61
+ { id: 'hq-sales-global', name: '해외영업팀' },
62
+ ],
63
+ },
64
+ ],
65
+ },
66
+ {
67
+ id: 'rnd',
68
+ name: 'R&D센터',
69
+ children: [
70
+ { id: 'rnd-ml', name: '머신러닝팀' },
71
+ { id: 'rnd-data', name: '데이터팀' },
72
+ ],
73
+ },
74
+ ],
75
+ };
76
+
77
+ const orgCollection = createTreeCollection<Node>({
78
+ rootNode: orgSample,
79
+ nodeToValue: (node) => node.id,
80
+ nodeToString: (node) => node.name,
81
+ });
82
+
83
+ const allBranchIds = (function collect(n: Node): string[] {
84
+ if (!n.children) return [];
85
+ return n.children.flatMap((c) => (c.children ? [c.id, ...collect(c)] : []));
86
+ })(orgSample);
87
+
88
+ const renderNode = ({
89
+ node,
90
+ nodeState,
91
+ }: {
92
+ node: Node;
93
+ nodeState: { isBranch: boolean };
94
+ }) =>
95
+ nodeState.isBranch ? (
96
+ <Tree.BranchControl>
97
+ <Tree.BranchIndicator />
98
+ <Tree.BranchText>{node.name}</Tree.BranchText>
99
+ </Tree.BranchControl>
100
+ ) : (
101
+ <Tree.Item>
102
+ <Tree.ItemText>{node.name}</Tree.ItemText>
103
+ </Tree.Item>
104
+ );
105
+
106
+ const meta = {
107
+ title: 'Components/Tree',
108
+ component: Tree.Root,
109
+ parameters: { layout: 'padded' },
110
+ } satisfies Meta<typeof Tree.Root>;
111
+
112
+ export default meta;
113
+
114
+ type Story = StoryObj<typeof meta>;
115
+
116
+ /**
117
+ * `elbow` guides — the innermost rail grows a horizontal foot so each
118
+ * row connects to its parent column like a classic file-tree.
119
+ */
120
+ export const WithElbowGuides: Story = {
121
+ args: {
122
+ collection: orgCollection,
123
+ 'aria-label': '조직',
124
+ defaultExpandedValue: allBranchIds,
125
+ defaultSelectedValue: ['hq-dev-frontend-web'],
126
+ },
127
+ render: (args) => (
128
+ <Box maxWidth="360px">
129
+ <Tree.Root {...args}>
130
+ <Tree.Tree>
131
+ <Tree.Node
132
+ indentGuide={<Tree.BranchIndentGuide elbow />}
133
+ render={renderNode}
134
+ />
135
+ </Tree.Tree>
136
+ </Tree.Root>
137
+ </Box>
138
+ ),
139
+ };
140
+
141
+ /**
142
+ * `guideColor` — customize the guide line color with any Chakra color
143
+ * token. The default `border.default` flips with color mode; pick a
144
+ * stronger token (or any palette color) for more contrast.
145
+ */
146
+ export const GuideColor: Story = {
147
+ args: {
148
+ collection: orgCollection,
149
+ 'aria-label': '조직',
150
+ defaultExpandedValue: allBranchIds,
151
+ },
152
+ render: (args) => (
153
+ <HStack align="flex-start" gap={8}>
154
+ {(
155
+ [
156
+ ['border.default (기본)', undefined],
157
+ ['border.strong', 'border.strong'],
158
+ ['primary.light', 'primary.light'],
159
+ ] as const
160
+ ).map(([label, guideColor]) => (
161
+ <Stack key={label} gap={2} width="280px">
162
+ <Text fontSize="sm" fontWeight="semibold" color="fg.muted">
163
+ {label}
164
+ </Text>
165
+ <Tree.Root {...args}>
166
+ <Tree.Tree>
167
+ <Tree.Node
168
+ indentGuide={
169
+ <Tree.BranchIndentGuide elbow guideColor={guideColor} />
170
+ }
171
+ render={renderNode}
172
+ />
173
+ </Tree.Tree>
174
+ </Tree.Root>
175
+ </Stack>
176
+ ))}
177
+ </HStack>
178
+ ),
179
+ };
180
+
181
+ /**
182
+ * Plain vertical rails (default) vs. `elbow` guides, side by side.
183
+ */
184
+ export const ElbowGuideComparison: Story = {
185
+ args: {
186
+ collection: orgCollection,
187
+ 'aria-label': '조직',
188
+ defaultExpandedValue: allBranchIds,
189
+ },
190
+ render: (args) => (
191
+ <HStack align="flex-start" gap={8}>
192
+ <Stack gap={2} width="320px">
193
+ <Text fontSize="sm" fontWeight="semibold" color="fg.muted">
194
+ 기본 — 수직 레일
195
+ </Text>
196
+ <Tree.Root {...args}>
197
+ <Tree.Tree>
198
+ <Tree.Node
199
+ indentGuide={<Tree.BranchIndentGuide />}
200
+ render={renderNode}
201
+ />
202
+ </Tree.Tree>
203
+ </Tree.Root>
204
+ </Stack>
205
+ <Stack gap={2} width="320px">
206
+ <Text fontSize="sm" fontWeight="semibold" color="fg.muted">
207
+ elbow — ├ / └ 가이드 라인
208
+ </Text>
209
+ <Tree.Root {...args}>
210
+ <Tree.Tree>
211
+ <Tree.Node
212
+ indentGuide={<Tree.BranchIndentGuide elbow />}
213
+ render={renderNode}
214
+ />
215
+ </Tree.Tree>
216
+ </Tree.Root>
217
+ </Stack>
218
+ </HStack>
219
+ ),
220
+ };
@@ -23,7 +23,35 @@ export type TreeBranchTriggerProps = TreeViewBranchTriggerProps;
23
23
  export type TreeBranchIndicatorProps = TreeViewBranchIndicatorProps;
24
24
  export type TreeBranchTextProps = TreeViewBranchTextProps;
25
25
  export type TreeBranchContentProps = TreeViewBranchContentProps;
26
- export type TreeBranchIndentGuideProps = TreeViewBranchIndentGuideProps;
26
+ export type TreeBranchIndentGuideProps = TreeViewBranchIndentGuideProps & {
27
+ /**
28
+ * Draw `├` / `└` guide lines instead of a plain vertical rail: every
29
+ * row gets a horizontal foot joining it to its parent rail, middle
30
+ * rows keep the vertical (`├`), and the last row of each group stops
31
+ * at its centre (`└`). Off by default to preserve the existing
32
+ * plain-vertical-rail look. See `TreeBranchIndentGuide`.
33
+ * @default false
34
+ */
35
+ elbow?: boolean;
36
+ /**
37
+ * Length of the elbow foot — the horizontal cross-stroke that joins
38
+ * the rail to the row content, drawn from the parent rail column
39
+ * toward the text. In elbow mode each level indents 4px further than
40
+ * the plain-rail default, and since the default foot length doesn't
41
+ * grow with it, the foot ends 8px short of the row text. Any Chakra
42
+ * width value. Only applies when `elbow` is set.
43
+ * @default 'calc(var(--tree-indentation) - 4px)'
44
+ */
45
+ footLength?: string | number;
46
+ /**
47
+ * Color of the guide lines (rail, elbow foot and vertical segments).
48
+ * Any Chakra color token. The default is a mode-flipping semantic
49
+ * token, so it darkens in light mode and lightens in dark mode
50
+ * automatically.
51
+ * @default 'border.default'
52
+ */
53
+ guideColor?: string;
54
+ };
27
55
  export type TreeItemProps = TreeViewItemProps;
28
56
  export type TreeItemTextProps = TreeViewItemTextProps;
29
57
  export type TreeItemIndicatorProps = TreeViewItemIndicatorProps;