@noya-app/noya-designsystem 0.1.43 → 0.1.45

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 (44) hide show
  1. package/.turbo/turbo-build.log +12 -9
  2. package/CHANGELOG.md +15 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +323 -80
  5. package/dist/index.d.ts +323 -80
  6. package/dist/index.js +2126 -1373
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +2012 -1272
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +3 -3
  11. package/src/components/AnimatePresence.tsx +10 -1
  12. package/src/components/Avatar.tsx +2 -0
  13. package/src/components/Checkbox.tsx +6 -1
  14. package/src/components/Combobox.tsx +28 -14
  15. package/src/components/ComboboxMenu.tsx +30 -15
  16. package/src/components/CommandPalette.tsx +69 -0
  17. package/src/components/ContextMenu.tsx +33 -134
  18. package/src/components/DropdownMenu.tsx +47 -155
  19. package/src/components/Fade.tsx +62 -0
  20. package/src/components/InputField.tsx +109 -133
  21. package/src/components/Label.tsx +81 -7
  22. package/src/components/LabeledField.tsx +112 -0
  23. package/src/components/ListView.tsx +55 -52
  24. package/src/components/Popover.tsx +12 -1
  25. package/src/components/SearchCompletionMenu.tsx +206 -0
  26. package/src/components/SegmentedControl.tsx +5 -2
  27. package/src/components/SelectMenu.tsx +108 -124
  28. package/src/components/SidebarList.tsx +252 -0
  29. package/src/components/Slider.tsx +18 -26
  30. package/src/components/Text.tsx +1 -0
  31. package/src/components/TextArea.tsx +4 -1
  32. package/src/components/Toolbar.tsx +6 -2
  33. package/src/components/TreeView.tsx +3 -0
  34. package/src/components/WorkspaceLayout.tsx +38 -18
  35. package/src/components/internal/Menu.tsx +58 -10
  36. package/src/components/internal/MenuViewport.tsx +151 -0
  37. package/src/components/internal/SelectItem.tsx +111 -0
  38. package/src/hooks/useIndent.ts +12 -0
  39. package/src/hooks/useLabel.ts +51 -0
  40. package/src/hooks/usePreservePanelSize.tsx +50 -19
  41. package/src/index.tsx +12 -5
  42. package/src/utils/combobox.ts +4 -1
  43. package/src/utils/createSectionedMenu.ts +11 -8
  44. package/src/utils/selection.ts +36 -0
@@ -7,6 +7,7 @@ import React, {
7
7
  useEffect,
8
8
  useRef,
9
9
  } from "react";
10
+ import { useLabel } from "../hooks/useLabel";
10
11
  import { cx } from "../utils/classNames";
11
12
 
12
13
  export const useAutoResize = (value: string | undefined) => {
@@ -39,6 +40,7 @@ export const TextArea = memo(
39
40
  } & React.TextareaHTMLAttributes<HTMLTextAreaElement>,
40
41
  forwardedRef: React.ForwardedRef<HTMLTextAreaElement>
41
42
  ) {
43
+ const { fieldId: id } = useLabel({ fieldId: rest.id });
42
44
  const autoResizeRef = useAutoResize(
43
45
  autoResize ? value || rest.placeholder || "" : undefined
44
46
  );
@@ -67,8 +69,9 @@ export const TextArea = memo(
67
69
  `font-sans text-heading5 font-normal text-text bg-input-background flex-1 py-1 px-1.5 border-none outline-none min-h-6 w-full rounded resize-none placeholder:text-text-disabled focus:ring-2 focus:ring-primary read-only:text-text-disabled focus:z-interactable transition-all`,
68
70
  className
69
71
  )}
70
- ref={handleRef}
71
72
  {...rest}
73
+ id={id}
74
+ ref={handleRef}
72
75
  onChange={handleChange}
73
76
  value={value}
74
77
  />
@@ -6,7 +6,10 @@ import { Button } from "./Button";
6
6
  import { DropdownMenu } from "./DropdownMenu";
7
7
  import { renderIcon } from "./Icons";
8
8
  import type { MenuItem } from "./internal/Menu";
9
- import { getKeyboardShortcutsForMenuItems } from "./internal/Menu";
9
+ import {
10
+ getKeyboardShortcutsForMenuItems,
11
+ SectionHeader,
12
+ } from "./internal/Menu";
10
13
  import { Spacer } from "./Spacer";
11
14
 
12
15
  export interface ToolbarProps<T extends string = string> {
@@ -82,7 +85,8 @@ export function ToolbarMenu<T extends string>({
82
85
  return (
83
86
  <>
84
87
  {items.map((item, i) => {
85
- if (typeof item === "string") return null;
88
+ if (item.type === "separator") return null;
89
+ if (item.type === "sectionHeader") return <SectionHeader {...item} />;
86
90
 
87
91
  if (!item.items) {
88
92
  return (
@@ -12,6 +12,7 @@ import { Spacer } from "./Spacer";
12
12
  type TreeRowBaseProps = {
13
13
  icon?: Exclude<ReactNode, string> | IconName;
14
14
  expanded?: boolean;
15
+ chevronClassName?: string;
15
16
  onClickChevron?: ({ altKey }: { altKey: boolean }) => void;
16
17
  };
17
18
 
@@ -23,6 +24,7 @@ const TreeRow = forwardRefGeneric(function TreeRow<MenuItemType extends string>(
23
24
  icon,
24
25
  expanded,
25
26
  onClickChevron,
27
+ chevronClassName,
26
28
  children,
27
29
  ...rest
28
30
  }: TreeViewRowProps<MenuItemType>,
@@ -46,6 +48,7 @@ const TreeRow = forwardRefGeneric(function TreeRow<MenuItemType extends string>(
46
48
  <Spacer.Horizontal size={19} />
47
49
  ) : (
48
50
  <IconButton
51
+ className={chevronClassName}
49
52
  iconName={expanded ? "ChevronDownIcon2" : "ChevronRightIcon2"}
50
53
  onClick={handleClickChevron}
51
54
  selected={rest.selected}
@@ -25,10 +25,11 @@ import {
25
25
  import { useWindowSize } from "../hooks/useWindowSize";
26
26
  import { cx } from "../utils/classNames";
27
27
 
28
- interface Props {
28
+ export interface WorkspaceLayoutProps {
29
29
  id?: string;
30
30
  className?: string;
31
31
  style?: React.CSSProperties;
32
+ theme?: "light" | "dark";
32
33
  autoSavePrefix?: string;
33
34
  left?: React.ReactNode;
34
35
  leftOptions?: SideOptions;
@@ -57,6 +58,7 @@ export type SideOptions = {
57
58
  */
58
59
  initialSize?: number | string;
59
60
  minSize?: number | string;
61
+ maxSize?: number | string;
60
62
  };
61
63
 
62
64
  export const WorkspaceLayout = forwardRef(function WorkspaceLayout(
@@ -72,19 +74,22 @@ export const WorkspaceLayout = forwardRef(function WorkspaceLayout(
72
74
  onChangeLayoutState,
73
75
  leftOptions: {
74
76
  initialSize: leftInitialSize = 280,
75
- minSize: leftMinSize,
77
+ minSize: leftMinSize = 200,
78
+ maxSize: leftMaxSize = 500,
76
79
  resizable: leftResizable = true,
77
80
  style: leftStyle,
78
81
  className: leftClassName,
79
82
  } = {},
80
83
  rightOptions: {
81
84
  initialSize: rightInitialSize = 280,
82
- minSize: rightMinSize,
85
+ minSize: rightMinSize = 200,
86
+ maxSize: rightMaxSize = 500,
83
87
  resizable: rightResizable = true,
84
88
  style: rightStyle,
85
89
  className: rightClassName,
86
90
  } = {},
87
- }: Props,
91
+ theme,
92
+ }: WorkspaceLayoutProps,
88
93
  forwardedRef: React.ForwardedRef<IWorkspaceLayout>
89
94
  ) {
90
95
  const windowSize = useWindowSize();
@@ -100,6 +105,10 @@ export const WorkspaceLayout = forwardRef(function WorkspaceLayout(
100
105
  leftMinSize !== undefined ? getPercentage(leftMinSize) : undefined;
101
106
  const rightSidebarMinPercentage =
102
107
  rightMinSize !== undefined ? getPercentage(rightMinSize) : undefined;
108
+ const leftSidebarMaxPercentage =
109
+ leftMaxSize !== undefined ? getPercentage(leftMaxSize) : undefined;
110
+ const rightSidebarMaxPercentage =
111
+ rightMaxSize !== undefined ? getPercentage(rightMaxSize) : undefined;
103
112
 
104
113
  const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
105
114
  const leftSidebarRef = useRef<ImperativePanelHandle>(null);
@@ -189,6 +198,7 @@ export const WorkspaceLayout = forwardRef(function WorkspaceLayout(
189
198
  id={id}
190
199
  className={cx("flex flex-col bg-canvas-background", className)}
191
200
  style={style}
201
+ data-theme={theme}
192
202
  >
193
203
  {toolbar}
194
204
  <div className="flex flex-row flex-1">
@@ -212,11 +222,20 @@ export const WorkspaceLayout = forwardRef(function WorkspaceLayout(
212
222
  ref={leftSidebarRef}
213
223
  defaultSize={leftSidebarPercentage}
214
224
  minSize={leftSidebarMinPercentage ?? leftSidebarPercentage}
215
- {...(!leftResizable && { maxSize: leftSidebarPercentage })}
225
+ maxSize={
226
+ leftSidebarMaxPercentage ??
227
+ (!leftResizable ? leftSidebarPercentage : undefined)
228
+ }
216
229
  collapsible
217
230
  >
218
231
  {internalLayoutState?.leftSidebarCollapsed ? null : (
219
- <PanelInner style={leftStyle} className={leftClassName}>
232
+ <PanelInner
233
+ style={leftStyle}
234
+ className={cx(
235
+ "bg-sidebar-background flex-col",
236
+ leftClassName
237
+ )}
238
+ >
220
239
  {left}
221
240
  </PanelInner>
222
241
  )}
@@ -231,7 +250,7 @@ export const WorkspaceLayout = forwardRef(function WorkspaceLayout(
231
250
  minSize={10}
232
251
  className="flex relative"
233
252
  >
234
- {children}
253
+ <PanelInner className="bg-canvas-background">{children}</PanelInner>
235
254
  </Panel>
236
255
  {right && (
237
256
  <>
@@ -243,13 +262,20 @@ export const WorkspaceLayout = forwardRef(function WorkspaceLayout(
243
262
  ref={rightSidebarRef}
244
263
  minSize={rightSidebarMinPercentage ?? rightSidebarPercentage}
245
264
  defaultSize={rightSidebarPercentage}
246
- {...(!rightResizable && {
247
- maxSize: rightSidebarPercentage,
248
- })}
265
+ maxSize={
266
+ rightSidebarMaxPercentage ??
267
+ (!rightResizable ? rightSidebarPercentage : undefined)
268
+ }
249
269
  collapsible
250
270
  >
251
271
  {internalLayoutState?.rightSidebarCollapsed ? null : (
252
- <PanelInner style={rightStyle} className={rightClassName}>
272
+ <PanelInner
273
+ style={rightStyle}
274
+ className={cx(
275
+ "bg-sidebar-background flex-col",
276
+ rightClassName
277
+ )}
278
+ >
253
279
  {right}
254
280
  </PanelInner>
255
281
  )}
@@ -272,13 +298,7 @@ const PanelInner = memo(function PanelInner({
272
298
  className?: string;
273
299
  }) {
274
300
  return (
275
- <div
276
- style={style}
277
- className={cx(
278
- "absolute inset-0 flex flex-col bg-sidebar-background",
279
- className
280
- )}
281
- >
301
+ <div style={style} className={cx("absolute inset-0 flex", className)}>
282
302
  {children}
283
303
  </div>
284
304
  );
@@ -1,11 +1,15 @@
1
1
  import { getShortcutDisplayParts } from "@noya-app/noya-keymap";
2
2
  import React, { memo, ReactNode } from "react";
3
3
  import { useDesignSystemConfiguration } from "../../contexts/DesignSystemConfiguration";
4
+ import { cx } from "../../utils/classNames";
4
5
  import withSeparatorElements from "../../utils/withSeparatorElements";
5
6
  import { IconName } from "../Icons";
7
+ import { Spacer } from "../Spacer";
6
8
  import { textStyles } from "../Text";
7
9
 
8
- export const SEPARATOR_ITEM = "separator";
10
+ export type SeparatorItem = {
11
+ type: "separator";
12
+ };
9
13
 
10
14
  // From electron
11
15
  export type MenuItemRole =
@@ -66,11 +70,22 @@ export type RegularMenuItem<T extends string> = {
66
70
  icon?: Exclude<ReactNode, string> | IconName;
67
71
  items?: MenuItem<T>[];
68
72
  role?: MenuItemRole;
73
+ type?: undefined;
74
+ alwaysInclude?: boolean;
75
+ };
76
+
77
+ export type SectionHeaderMenuItem = {
78
+ type: "sectionHeader";
79
+ id: string;
80
+ title: string;
81
+ maxVisibleItems?: number;
82
+ variant?: "normal" | "label";
69
83
  };
70
84
 
71
85
  export type MenuItem<T extends string> =
72
- | typeof SEPARATOR_ITEM
73
- | RegularMenuItem<T>;
86
+ | SeparatorItem
87
+ | RegularMenuItem<T>
88
+ | SectionHeaderMenuItem;
74
89
 
75
90
  // Extract type T of RegularMenuItem<T> within MenuItem<T>
76
91
  export type ExtractMenuItemType<T> =
@@ -80,17 +95,17 @@ export const CHECKBOX_WIDTH = 16;
80
95
  export const CHECKBOX_RIGHT_INSET = 8;
81
96
 
82
97
  export const styles = {
83
- separatorStyle: "h-px bg-divider mx-[-4px] my-1",
98
+ separatorStyle: "h-px bg-divider mx-4 my-1",
84
99
 
85
100
  itemStyle: ({ disabled }: { disabled?: boolean }) => `
86
101
  flex-none select-none cursor-pointer rounded
87
- py-1.5 px-2
102
+ py-1.5 px-3
88
103
  focus:outline-none focus:text-white focus:bg-primary
89
104
  focus:kbd:text-white
90
105
  active:bg-primary-light
91
106
  transition-colors
92
107
  flex items-center
93
- font-sans text-button font-medium
108
+ font-sans text-button font-medium
94
109
  ${disabled ? "text-text-disabled" : ""}
95
110
  `,
96
111
 
@@ -106,16 +121,16 @@ export const styles = {
106
121
  bg-popover-background
107
122
  text-text
108
123
  shadow-[0_2px_4px_rgba(0,0,0,0.2),_0_0_12px_rgba(0,0,0,0.1)]
109
- p-1
110
- border border-popover-divider
111
124
  z-menu
125
+ py-1
112
126
  `,
113
127
  };
114
128
 
115
129
  function getKeyboardShortcuts<T extends string>(
116
130
  item: MenuItem<T>
117
131
  ): [string, T][] {
118
- if (item === SEPARATOR_ITEM) return [];
132
+ if (item.type === "separator") return [];
133
+ if (item.type === "sectionHeader") return [];
119
134
 
120
135
  if (item.items) return item.items.flatMap(getKeyboardShortcuts);
121
136
 
@@ -143,7 +158,11 @@ const ShortcutElement = ({
143
158
  fixedWidth?: boolean;
144
159
  }) => (
145
160
  <kbd
146
- className={`${textStyles.small} text-text-disabled ${fixedWidth && "w-[0.9rem] text-center"}`}
161
+ className={cx(
162
+ textStyles.small,
163
+ "text-inherit opacity-60 tabular-nums",
164
+ fixedWidth && "w-[0.9rem] text-center"
165
+ )}
147
166
  >
148
167
  {children}
149
168
  </kbd>
@@ -178,3 +197,32 @@ export const KeyboardShortcut = memo(function KeyboardShortcut({
178
197
  </>
179
198
  );
180
199
  });
200
+
201
+ export const SectionHeader = memo(function SectionHeader({
202
+ title,
203
+ variant = "normal",
204
+ id,
205
+ isFirst,
206
+ indented,
207
+ }: Omit<SectionHeaderMenuItem, "maxVisibleItems" | "type"> & {
208
+ isFirst?: boolean;
209
+ indented?: boolean;
210
+ }) {
211
+ return (
212
+ <span
213
+ id={id}
214
+ className={cx(
215
+ variant === "label"
216
+ ? `text-label ${textStyles.label} !font-bold text-text-disabled`
217
+ : "font-sans text-heading5 font-normal",
218
+ "bg-listview-raised-background flex items-center py-1.5 px-4",
219
+ isFirst && "-mt-1"
220
+ )}
221
+ >
222
+ {indented && (
223
+ <Spacer.Horizontal size={CHECKBOX_WIDTH - CHECKBOX_RIGHT_INSET} />
224
+ )}
225
+ {title}
226
+ </span>
227
+ );
228
+ });
@@ -0,0 +1,151 @@
1
+ import { ChevronRightIcon } from "@noya-app/noya-icons";
2
+ import * as RadixContextMenu from "@radix-ui/react-context-menu";
3
+ import * as RadixDropdownMenu from "@radix-ui/react-dropdown-menu";
4
+ import React from "react";
5
+ import { MenuItem, SectionHeader, styles } from "./Menu";
6
+ import { SelectItem } from "./SelectItem";
7
+
8
+ type MenuSeparatorComponent = React.FC<{ className?: string }>;
9
+ type MenuItemTextComponent = React.FC<{ children?: React.ReactNode }>;
10
+ type MenuItemComponent = React.ForwardRefExoticComponent<
11
+ React.RefAttributes<HTMLDivElement> &
12
+ React.PropsWithoutRef<{
13
+ className?: string;
14
+ disabled?: boolean;
15
+ children?: React.ReactNode;
16
+ value: string;
17
+ onSelect?: (event: any) => void;
18
+ }>
19
+ >;
20
+ type MenuCheckboxItemComponent = React.FC<{
21
+ checked?: boolean | "indeterminate";
22
+ disabled?: boolean;
23
+ onSelect?: (event: any) => void;
24
+ className?: string;
25
+ children?: React.ReactNode;
26
+ }>;
27
+
28
+ type MenuItemIndicatorComponent = React.FC<{
29
+ className?: string;
30
+ children?: React.ReactNode;
31
+ }>;
32
+
33
+ export type MenuComponents = {
34
+ Separator: MenuSeparatorComponent;
35
+ ItemText: MenuItemTextComponent;
36
+ Item: MenuItemComponent;
37
+ CheckboxItem?: MenuCheckboxItemComponent;
38
+ ItemIndicator?: MenuItemIndicatorComponent;
39
+ Sub?: typeof RadixDropdownMenu.Sub | typeof RadixContextMenu.Sub;
40
+ SubTrigger?:
41
+ | typeof RadixDropdownMenu.SubTrigger
42
+ | typeof RadixContextMenu.SubTrigger;
43
+ SubContent?:
44
+ | typeof RadixDropdownMenu.SubContent
45
+ | typeof RadixContextMenu.SubContent;
46
+ Portal?: typeof RadixDropdownMenu.Portal | typeof RadixContextMenu.Portal;
47
+ };
48
+
49
+ type MenuViewportProps<T extends string> = {
50
+ items: MenuItem<T>[];
51
+ Components: MenuComponents;
52
+ onSelect?: (value: any) => void;
53
+ };
54
+
55
+ export const MenuViewport = <T extends string>({
56
+ items,
57
+ Components,
58
+ onSelect,
59
+ }: MenuViewportProps<T>) => {
60
+ const hasCheckedItem = items.some(
61
+ (item) =>
62
+ item.type !== "separator" && item.type !== "sectionHeader" && item.checked
63
+ );
64
+
65
+ return (
66
+ <>
67
+ {items.map((item, index) => {
68
+ if (item.type === "separator") {
69
+ return (
70
+ <Components.Separator
71
+ key={index}
72
+ className={styles.separatorStyle}
73
+ />
74
+ );
75
+ }
76
+
77
+ if (item.type === "sectionHeader") {
78
+ return (
79
+ <SectionHeader
80
+ isFirst={index === 0}
81
+ key={item.id}
82
+ {...item}
83
+ indented={hasCheckedItem}
84
+ />
85
+ );
86
+ }
87
+
88
+ const value = item.value ?? "";
89
+
90
+ if (
91
+ item.items &&
92
+ Components.Sub &&
93
+ Components.SubTrigger &&
94
+ Components.SubContent &&
95
+ Components.Portal
96
+ ) {
97
+ return (
98
+ <Components.Sub key={value}>
99
+ <Components.SubTrigger
100
+ className={styles.itemStyle({ disabled: item.disabled })}
101
+ asChild
102
+ >
103
+ <SelectItem
104
+ value={value}
105
+ icon={item.icon}
106
+ checked={item.checked}
107
+ shortcut={item.shortcut}
108
+ onSelect={onSelect}
109
+ Components={Components}
110
+ indented={hasCheckedItem}
111
+ >
112
+ <div className="flex items-center flex-1">
113
+ {item.title ?? value}
114
+ </div>
115
+ <ChevronRightIcon className="-mr-1" />
116
+ </SelectItem>
117
+ </Components.SubTrigger>
118
+ <Components.Portal>
119
+ <Components.SubContent
120
+ alignOffset={-5}
121
+ className={styles.contentStyle}
122
+ >
123
+ <MenuViewport
124
+ items={item.items}
125
+ Components={Components}
126
+ onSelect={onSelect}
127
+ />
128
+ </Components.SubContent>
129
+ </Components.Portal>
130
+ </Components.Sub>
131
+ );
132
+ }
133
+
134
+ return (
135
+ <SelectItem
136
+ key={value}
137
+ value={value}
138
+ icon={item.icon}
139
+ checked={item.checked}
140
+ shortcut={item.shortcut}
141
+ onSelect={onSelect}
142
+ Components={Components}
143
+ indented={hasCheckedItem}
144
+ >
145
+ {item.title ?? value}
146
+ </SelectItem>
147
+ );
148
+ })}
149
+ </>
150
+ );
151
+ };
@@ -0,0 +1,111 @@
1
+ import { renderIcon } from "../Icons";
2
+
3
+ import { CheckIcon } from "@noya-app/noya-icons";
4
+ import * as Select from "@radix-ui/react-select";
5
+ import React, { useCallback } from "react";
6
+ import { Spacer } from "../Spacer";
7
+ import {
8
+ CHECKBOX_RIGHT_INSET,
9
+ CHECKBOX_WIDTH,
10
+ KeyboardShortcut,
11
+ styles,
12
+ } from "./Menu";
13
+ import { MenuComponents } from "./MenuViewport";
14
+
15
+ export const SelectItem = React.forwardRef(
16
+ (
17
+ {
18
+ children,
19
+ icon,
20
+ disabled,
21
+ checked,
22
+ Components,
23
+ value,
24
+ onSelect,
25
+ shortcut,
26
+ indented,
27
+ ...props
28
+ }: Select.SelectItemProps & {
29
+ icon?: React.ReactNode;
30
+ checked?: boolean | "indeterminate";
31
+ Components: MenuComponents;
32
+ onSelect?: (value: any) => void;
33
+ shortcut?: string;
34
+ indented?: boolean;
35
+ },
36
+ forwardedRef: React.ForwardedRef<HTMLDivElement>
37
+ ) => {
38
+ const handleSelectItem = useCallback(() => {
39
+ if (!value) return;
40
+ onSelect?.(value);
41
+ }, [onSelect, value]);
42
+
43
+ if (checked && Components.CheckboxItem) {
44
+ return (
45
+ <Components.CheckboxItem
46
+ checked={checked}
47
+ disabled={disabled}
48
+ onSelect={handleSelectItem}
49
+ className={styles.itemStyle({ disabled })}
50
+ >
51
+ {Components.ItemIndicator && (
52
+ <Components.ItemIndicator className={styles.itemIndicatorStyle}>
53
+ <CheckIcon />
54
+ </Components.ItemIndicator>
55
+ )}
56
+ {icon && (
57
+ <>
58
+ {renderIcon(icon)}
59
+ <Spacer.Horizontal size={8} />
60
+ </>
61
+ )}
62
+ {children}
63
+ {shortcut && (
64
+ <>
65
+ <Spacer.Horizontal />
66
+ <Spacer.Horizontal size={24} />
67
+ <KeyboardShortcut shortcut={shortcut} />
68
+ </>
69
+ )}
70
+ </Components.CheckboxItem>
71
+ );
72
+ }
73
+
74
+ return (
75
+ <div className="px-1">
76
+ <Components.Item
77
+ className={styles.itemStyle({ disabled })}
78
+ disabled={disabled}
79
+ value={value}
80
+ ref={forwardedRef}
81
+ onSelect={handleSelectItem}
82
+ {...props}
83
+ >
84
+ <div className="flex flex-1 items-center">
85
+ {checked ? (
86
+ <div className={styles.itemIndicatorStyle}>
87
+ <CheckIcon />
88
+ </div>
89
+ ) : indented ? (
90
+ <Spacer.Horizontal size={CHECKBOX_WIDTH - CHECKBOX_RIGHT_INSET} />
91
+ ) : null}
92
+ {icon && (
93
+ <>
94
+ {renderIcon(icon)}
95
+ <Spacer.Horizontal size={8} />
96
+ </>
97
+ )}
98
+ <Components.ItemText>{children}</Components.ItemText>
99
+ {shortcut && (
100
+ <>
101
+ <Spacer.Horizontal />
102
+ <Spacer.Horizontal size={24} />
103
+ <KeyboardShortcut shortcut={shortcut} />
104
+ </>
105
+ )}
106
+ </div>
107
+ </Components.Item>
108
+ </div>
109
+ );
110
+ }
111
+ );
@@ -0,0 +1,12 @@
1
+ import React from "react";
2
+
3
+ export const IndentContext = React.createContext<{
4
+ indentLevel: number;
5
+ }>({
6
+ indentLevel: 0,
7
+ });
8
+
9
+ export const useIndent = () => {
10
+ const { indentLevel } = React.useContext(IndentContext);
11
+ return indentLevel;
12
+ };
@@ -0,0 +1,51 @@
1
+ import React, { ReactNode } from "react";
2
+ import { LabelPosition } from "../components/Label";
3
+
4
+ export const LabelContext = React.createContext<{
5
+ fieldId?: string;
6
+ label?: React.ReactNode;
7
+ }>({
8
+ fieldId: "",
9
+ label: undefined,
10
+ });
11
+
12
+ type UseLabelParams = {
13
+ label?: ReactNode;
14
+ fieldId?: string;
15
+ };
16
+
17
+ export const useLabel = ({ label, fieldId }: UseLabelParams) => {
18
+ const { label: labelFromContext, fieldId: fieldIdFromContext } =
19
+ React.useContext(LabelContext);
20
+
21
+ return {
22
+ label: label ?? labelFromContext,
23
+ fieldId: fieldId ?? fieldIdFromContext,
24
+ };
25
+ };
26
+
27
+ export const LabelPositionContext =
28
+ React.createContext<LabelPositionContextValue>({
29
+ position: "start",
30
+ });
31
+
32
+ export const useLabelPosition = () => {
33
+ const { position } = React.useContext(LabelPositionContext);
34
+ return position;
35
+ };
36
+
37
+ export type LabelPositionContextValue = {
38
+ position: LabelPosition;
39
+ };
40
+
41
+ export type LabelWidthContextValue = {
42
+ width: number;
43
+ };
44
+ export const LabelWidthContext = React.createContext<LabelWidthContextValue>({
45
+ width: 100,
46
+ });
47
+
48
+ export const useLabelWidth = () => {
49
+ const { width } = React.useContext(LabelWidthContext);
50
+ return width;
51
+ };