@noya-app/noya-designsystem 0.1.44 → 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 +13 -10
  2. package/CHANGELOG.md +9 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +320 -78
  5. package/dist/index.d.ts +320 -78
  6. package/dist/index.js +2113 -1374
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +2000 -1274
  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 +6 -4
  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 +104 -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
@@ -12,17 +12,19 @@ import React, {
12
12
  useMemo,
13
13
  useState,
14
14
  } from "react";
15
+ import { useLabel, useLabelPosition } from "../hooks/useLabel";
15
16
  import { cx } from "../utils/classNames";
16
17
  import { Button } from "./Button";
17
18
  import { renderIcon } from "./Icons";
18
19
  import { MenuItem, RegularMenuItem, styles } from "./internal/Menu";
19
- import { Spacer } from "./Spacer";
20
+ import { MenuComponents, MenuViewport } from "./internal/MenuViewport";
21
+ import { InsetLabel } from "./Label";
20
22
 
21
23
  type Props<T extends string> = {
22
24
  id?: string;
23
25
  style?: React.CSSProperties;
24
26
  className?: string;
25
- menuItems: MenuItem<T>[];
27
+ items: MenuItem<T>[];
26
28
  value: T;
27
29
  onSelect?: (value: T) => void;
28
30
  placeholder?: string;
@@ -39,9 +41,6 @@ const readOnlyStyle: CSSProperties = {
39
41
  textAlign: "left",
40
42
  };
41
43
 
42
- export const labelTextStyles = `font-sans text-label uppercase text-text-disabled leading-[19px] font-bold text-[60%]`;
43
- const labelStyles = `pointer-events-none flex items-center absolute top-0 bottom-0 right-[1.3rem] z-label`;
44
-
45
44
  const scrollButtonStyles = `
46
45
  flex
47
46
  items-center
@@ -52,32 +51,109 @@ const scrollButtonStyles = `
52
51
  hover:text-text
53
52
  `;
54
53
 
54
+ interface SelectMenuTriggerProps {
55
+ id?: string;
56
+ style?: React.CSSProperties;
57
+ className?: string;
58
+ disabled?: boolean;
59
+ icon?: React.ReactNode;
60
+ placeholder?: string;
61
+ isOpen?: boolean;
62
+ onFocus?: FocusEventHandler;
63
+ onBlur?: FocusEventHandler;
64
+ label?: React.ReactNode;
65
+ }
66
+
67
+ const SelectMenuTrigger = memoGeneric(function SelectMenuTrigger({
68
+ style,
69
+ className,
70
+ disabled,
71
+ icon,
72
+ placeholder,
73
+ isOpen,
74
+ onFocus,
75
+ onBlur,
76
+ ...props
77
+ }: SelectMenuTriggerProps) {
78
+ const { label, fieldId: id } = useLabel({
79
+ label: props.label,
80
+ fieldId: props.id,
81
+ });
82
+ const labelPosition = useLabelPosition();
83
+
84
+ return (
85
+ <Select.SelectTrigger asChild onFocus={onFocus} onBlur={onBlur}>
86
+ <Button
87
+ id={id}
88
+ style={style}
89
+ className={cx(
90
+ className ?? "flex w-full gap-1.5",
91
+ "flex-1 focus:z-interactable relative",
92
+ isOpen && "ring-2 ring-primary"
93
+ )}
94
+ disabled={disabled}
95
+ >
96
+ {icon && <span className="pr-1.5">{renderIcon(icon)}</span>}
97
+ {label && labelPosition === "inset" && (
98
+ <InsetLabel className="pr-4" htmlFor={id}>
99
+ {label}
100
+ </InsetLabel>
101
+ )}
102
+ <span className="flex-1 flex">
103
+ <Select.Value placeholder={placeholder} />
104
+ </span>
105
+
106
+ <span className="px-0.5">
107
+ <DropdownChevronIcon
108
+ className={cx(
109
+ "transition-transform duration-100",
110
+ isOpen ? "rotate-180" : "rotate-0"
111
+ )}
112
+ />
113
+ </span>
114
+ </Button>
115
+ </Select.SelectTrigger>
116
+ );
117
+ });
118
+
119
+ const Components: MenuComponents = {
120
+ Item: Select.Item,
121
+ ItemText: Select.ItemText,
122
+ Separator: Select.Separator,
123
+ };
124
+
55
125
  export const SelectMenu = memoGeneric(function SelectMenu<
56
126
  T extends string = string,
57
127
  >({
58
- id,
59
128
  style,
60
129
  className,
61
- menuItems,
130
+ items,
62
131
  value,
63
132
  onSelect,
64
133
  placeholder,
65
134
  disabled,
66
135
  readOnly,
67
- label,
68
136
  open,
69
137
  onFocus,
70
138
  onBlur,
71
139
  onOpenChange,
72
140
  contentStyle,
141
+ ...props
73
142
  }: Props<T>) {
74
- const selectedItem = menuItems.find(
143
+ const selectedItem = items.find(
75
144
  (item): item is RegularMenuItem<T> =>
76
- typeof item !== "string" && item.value === value
145
+ item.type !== "separator" &&
146
+ item.type !== "sectionHeader" &&
147
+ item.value === value
77
148
  );
78
149
  const icon = selectedItem?.icon;
79
150
  const [internalOpen, setInternalOpen] = useState(open);
80
151
 
152
+ const { label, fieldId: id } = useLabel({
153
+ label: props.label,
154
+ fieldId: props.id,
155
+ });
156
+
81
157
  const handleOpenChange = (open: boolean) => {
82
158
  if (onOpenChange) {
83
159
  onOpenChange(open);
@@ -91,92 +167,35 @@ export const SelectMenu = memoGeneric(function SelectMenu<
91
167
  id={id}
92
168
  style={style}
93
169
  contentStyle={readOnlyStyle}
94
- className={cx(`${className} flex-1 focus:z-interactable`)}
170
+ className={cx(`${className} flex-1 focus:z-interactable gap-1.5`)}
95
171
  disabled={disabled}
96
172
  >
97
- {icon && (
98
- <>
99
- {renderIcon(icon)}
100
- <Spacer.Horizontal inline size={6} />
101
- </>
102
- )}
173
+ {icon && renderIcon(icon)}
103
174
  <span className="flex flex-1">{selectedItem?.title ?? value}</span>
104
175
  </Button>
105
176
  ),
106
177
  [icon, id, style, className, disabled, value, selectedItem]
107
178
  );
108
179
 
109
- const trigger = useMemo(
110
- () => (
111
- <Select.SelectTrigger asChild onFocus={onFocus} onBlur={onBlur}>
112
- <Button
113
- id={id}
114
- style={style}
115
- className={cx(
116
- className ?? "w-full",
117
- "flex-1 focus:z-interactable relative",
118
- internalOpen && "ring-2 ring-primary"
119
- )}
120
- disabled={disabled}
121
- >
122
- {icon && (
123
- <>
124
- {renderIcon(icon)}
125
- <Spacer.Horizontal inline size={6} />
126
- </>
127
- )}
128
- <span className="flex flex-1 mr-1.5">
129
- <Select.Value placeholder={placeholder} />
130
- </span>
131
- {label && (
132
- <label htmlFor={id} className={cx(labelStyles, labelTextStyles)}>
133
- {label}
134
- </label>
135
- )}
136
- <DropdownChevronIcon
137
- className={cx(
138
- "transition-transform duration-100",
139
- internalOpen ? "rotate-180" : "rotate-0"
140
- )}
141
- />
142
- </Button>
143
- </Select.SelectTrigger>
144
- ),
145
- [
146
- onFocus,
147
- onBlur,
148
- id,
149
- style,
150
- className,
151
- disabled,
152
- icon,
153
- placeholder,
154
- label,
155
- internalOpen,
156
- ]
157
- );
158
-
159
- if (readOnly) {
160
- return label ? (
161
- <div className="flex flex-col relative">
162
- {readOnlyButton}
163
- <label className={labelStyles} htmlFor={id}>
164
- {label}
165
- </label>
166
- </div>
167
- ) : (
168
- readOnlyButton
169
- );
170
- }
171
-
172
- return (
180
+ const content = (
173
181
  <Select.Root
174
182
  value={value}
175
183
  onValueChange={onSelect}
176
184
  open={open}
177
185
  onOpenChange={handleOpenChange}
178
186
  >
179
- {trigger}
187
+ <SelectMenuTrigger
188
+ id={id}
189
+ style={style}
190
+ className={className}
191
+ disabled={disabled}
192
+ icon={icon}
193
+ placeholder={placeholder}
194
+ isOpen={internalOpen}
195
+ onFocus={onFocus}
196
+ onBlur={onBlur}
197
+ label={label}
198
+ />
180
199
  <Select.Portal>
181
200
  <Select.Content
182
201
  className={styles.contentStyle}
@@ -196,19 +215,7 @@ export const SelectMenu = memoGeneric(function SelectMenu<
196
215
  />
197
216
  </Select.ScrollUpButton>
198
217
  <Select.Viewport>
199
- {menuItems.map((menuItem) => {
200
- if (typeof menuItem === "string") {
201
- return <Select.Separator className={styles.separatorStyle} />;
202
- }
203
-
204
- const value = menuItem.value ?? "";
205
-
206
- return (
207
- <SelectItem key={value} value={value} icon={menuItem.icon}>
208
- {menuItem.title ?? value}
209
- </SelectItem>
210
- );
211
- })}
218
+ <MenuViewport items={items} Components={Components} />
212
219
  </Select.Viewport>
213
220
  <Select.ScrollDownButton className={scrollButtonStyles}>
214
221
  <ChevronDownIcon />
@@ -217,33 +224,6 @@ export const SelectMenu = memoGeneric(function SelectMenu<
217
224
  </Select.Portal>
218
225
  </Select.Root>
219
226
  );
220
- });
221
227
 
222
- const SelectItem = React.forwardRef(
223
- (
224
- {
225
- children,
226
- icon,
227
- disabled,
228
- ...props
229
- }: Select.SelectItemProps & { icon?: React.ReactNode },
230
- forwardedRef: React.ForwardedRef<HTMLDivElement>
231
- ) => {
232
- return (
233
- <Select.Item
234
- className={styles.itemStyle({ disabled })}
235
- disabled={disabled}
236
- {...props}
237
- ref={forwardedRef}
238
- >
239
- {icon && (
240
- <>
241
- {renderIcon(icon)}
242
- <Spacer.Horizontal size={8} />
243
- </>
244
- )}
245
- <Select.ItemText>{children}</Select.ItemText>
246
- </Select.Item>
247
- );
248
- }
249
- );
228
+ return readOnly ? readOnlyButton : content;
229
+ });
@@ -0,0 +1,252 @@
1
+ import { useFileDropTarget } from "@noya-app/react-utils";
2
+ import React, { useRef, useState } from "react";
3
+ import { cssVars } from "../theme";
4
+ import { cx } from "../utils/classNames";
5
+ import { updateSelection } from "../utils/selection";
6
+ import { MenuItem } from "./internal/Menu";
7
+ import type { ListViewRootProps } from "./ListView";
8
+ import { ListView } from "./ListView";
9
+ import { TreeView } from "./TreeView";
10
+
11
+ interface SidebarListProps<T, M extends string = string> {
12
+ className?: string;
13
+ items: T[];
14
+ getId: (item: T) => string;
15
+ getName: (item: T) => string;
16
+ getExpanded?: (item: T) => boolean | undefined;
17
+ expandable?: boolean;
18
+ menuItems?: MenuItem<M>[];
19
+ onSelectMenuItem?: (action: M, selectedItems: T[]) => void;
20
+ onRename?: (itemId: string, newName: string) => void;
21
+ renderThumbnail?: (item: T) => React.ReactNode;
22
+ renderHoverAction?: (item: T, selected: boolean) => React.ReactNode;
23
+ renderDetail?: (item: T) => React.ReactNode;
24
+ onSelectionChange?: (selectedItems: T[]) => void;
25
+ scrollable?: boolean;
26
+ itemRoleDescription?: string;
27
+ /** Position of the detail content. Defaults to 'end'. */
28
+ detailPosition?: "end" | "below";
29
+ /** Size of the list items. Defaults to 'medium'. */
30
+ size?: "medium" | "large";
31
+ /** For testing: Override the hover state with a specific item ID */
32
+ testHoveredId?: string;
33
+ /** For testing: Override the renaming state with a specific item ID */
34
+ testRenamingId?: string;
35
+ setExpanded?: (item: T, expanded: boolean) => void;
36
+ acceptsDrop?: ListViewRootProps["acceptsDrop"];
37
+ onMoveItem?: ListViewRootProps["onMoveItem"];
38
+ getDepth?: (item: T) => number;
39
+ onFilesDrop?: (event: React.DragEvent<Element>) => void;
40
+ }
41
+
42
+ export function SidebarList<T, M extends string = string>({
43
+ className,
44
+ items,
45
+ getId,
46
+ getName,
47
+ expandable,
48
+ getExpanded,
49
+ getDepth,
50
+ setExpanded,
51
+ menuItems,
52
+ onSelectMenuItem,
53
+ onRename,
54
+ renderThumbnail,
55
+ renderHoverAction,
56
+ renderDetail,
57
+ onSelectionChange,
58
+ itemRoleDescription = "clickable item",
59
+ detailPosition = "end",
60
+ size = "medium",
61
+ testHoveredId,
62
+ testRenamingId,
63
+ scrollable,
64
+ acceptsDrop,
65
+ onMoveItem,
66
+ onFilesDrop,
67
+ }: SidebarListProps<T, M>) {
68
+ const [selectedIds, setSelectedIds] = useState<string[]>([]);
69
+ const [internalHoveredId, setHoveredId] = useState<string>();
70
+ const [internalRenamingId, setRenamingId] = useState<string>();
71
+
72
+ const hoveredId = internalHoveredId ?? testHoveredId;
73
+ const renamingId = testRenamingId ?? internalRenamingId;
74
+
75
+ const handleSelect = (itemId: string, event?: ListView.ClickInfo) => {
76
+ const newSelectedIds = updateSelection(
77
+ items.map(getId),
78
+ selectedIds,
79
+ itemId,
80
+ event
81
+ );
82
+ setSelectedIds(newSelectedIds);
83
+
84
+ if (onSelectionChange) {
85
+ const selectedItems = items.filter((item) =>
86
+ newSelectedIds.includes(getId(item))
87
+ );
88
+ onSelectionChange(selectedItems);
89
+ }
90
+ };
91
+
92
+ const handleMenuAction = (action: M) => {
93
+ if (!onSelectMenuItem) return;
94
+
95
+ const selectedItems = items.filter((item) =>
96
+ selectedIds.includes(getId(item))
97
+ );
98
+ onSelectMenuItem(action, selectedItems);
99
+ };
100
+
101
+ const ViewComponent = expandable ? TreeView : ListView;
102
+
103
+ const fileDropTargetRef = useRef<HTMLDivElement>(null);
104
+ const { isDropTargetActive, dropTargetProps } = useFileDropTarget(
105
+ fileDropTargetRef,
106
+ onFilesDrop
107
+ );
108
+
109
+ return (
110
+ <ViewComponent.Root
111
+ variant="bare"
112
+ className={cx(className, isDropTargetActive && "bg-primary-pastel")}
113
+ containerRef={fileDropTargetRef}
114
+ scrollable={scrollable}
115
+ data={items}
116
+ keyExtractor={getId}
117
+ role="listbox"
118
+ aria-orientation="vertical"
119
+ aria-multiselectable={true}
120
+ expandable={expandable}
121
+ sortable={expandable}
122
+ acceptsDrop={acceptsDrop}
123
+ onMoveItem={onMoveItem}
124
+ {...dropTargetProps}
125
+ renderItem={(
126
+ item: T,
127
+ _: number,
128
+ { isDragging }: { isDragging: boolean }
129
+ ) => {
130
+ const id = getId(item);
131
+ const isHovered = hoveredId === id && !isDragging;
132
+ const expanded = getExpanded?.(item);
133
+ const isSelected = selectedIds.includes(id);
134
+ const isRenaming = renamingId === id;
135
+
136
+ const thumbnail = renderThumbnail?.(item);
137
+ const action =
138
+ isHovered && !isRenaming && renderHoverAction?.(item, isSelected);
139
+ const end =
140
+ action ?? (detailPosition === "end" && renderDetail?.(item));
141
+ const below = detailPosition === "below" && renderDetail?.(item);
142
+
143
+ return (
144
+ <ViewComponent.Row
145
+ id={id}
146
+ key={id}
147
+ role="option"
148
+ sortable={expandable}
149
+ aria-roledescription={itemRoleDescription}
150
+ aria-label={getName(item)}
151
+ aria-selected={isSelected}
152
+ className="cursor-pointer p-1"
153
+ chevronClassName={expandable ? "relative left-2" : undefined}
154
+ hovered={isHovered}
155
+ selected={isSelected}
156
+ expanded={expanded}
157
+ depth={getDepth?.(item)}
158
+ onHoverChange={(hovered: boolean) =>
159
+ setHoveredId(hovered ? id : undefined)
160
+ }
161
+ onPress={(e: ListView.ClickInfo) => handleSelect(id, e)}
162
+ onKeyDown={(e: React.KeyboardEvent) => {
163
+ if (e.key !== "Enter" && e.key !== " ") return;
164
+
165
+ e.preventDefault();
166
+ handleSelect(id, e);
167
+ }}
168
+ menuItems={menuItems}
169
+ onSelectMenuItem={handleMenuAction}
170
+ onMenuOpenChange={(open: boolean) => {
171
+ if (open && !isSelected) {
172
+ handleSelect(id, {
173
+ shiftKey: false,
174
+ altKey: false,
175
+ metaKey: false,
176
+ ctrlKey: false,
177
+ });
178
+ }
179
+ }}
180
+ tabIndex={0}
181
+ onClickChevron={() => {
182
+ if (expanded === undefined) return;
183
+ setExpanded?.(item, !expanded);
184
+ }}
185
+ >
186
+ <div className="flex flex-1 items-center px-2 gap-3">
187
+ {thumbnail && (
188
+ <div
189
+ className={cx(
190
+ "rounded overflow-hidden flex-none flex items-center justify-center",
191
+ size === "medium" ? "w-8 h-8" : "w-16 h-16"
192
+ )}
193
+ style={{
194
+ backgroundColor: cssVars.colors.inputBackground,
195
+ }}
196
+ tabIndex={-1}
197
+ >
198
+ {thumbnail}
199
+ </div>
200
+ )}
201
+ <div className="flex flex-col flex-1 w-0">
202
+ <div className="flex items-center min-h-[27px] flex-1 gap-2">
203
+ <div className="flex-1 w-0 flex items-center" tabIndex={-1}>
204
+ {isRenaming ? (
205
+ <ViewComponent.EditableRowTitle
206
+ value={getName(item)}
207
+ placeholder="Enter name"
208
+ className={cx(
209
+ "font-medium",
210
+ testRenamingId && "bg-primary-pastel"
211
+ )}
212
+ autoFocus
213
+ onSubmitEditing={(value: string) => {
214
+ if (value !== getName(item) && onRename) {
215
+ onRename(id, value);
216
+ }
217
+ setRenamingId(undefined);
218
+ }}
219
+ />
220
+ ) : (
221
+ <ViewComponent.RowTitle
222
+ className="font-medium"
223
+ onDoubleClick={(e: React.MouseEvent) => {
224
+ e.stopPropagation();
225
+ if (onRename) {
226
+ setRenamingId(id);
227
+ }
228
+ }}
229
+ >
230
+ {getName(item)}
231
+ </ViewComponent.RowTitle>
232
+ )}
233
+ </div>
234
+ {end && (
235
+ <div className="flex items-center gap-2" tabIndex={-1}>
236
+ {end}
237
+ </div>
238
+ )}
239
+ </div>
240
+ {below && (
241
+ <div className="flex items-center" tabIndex={-1}>
242
+ {below}
243
+ </div>
244
+ )}
245
+ </div>
246
+ </div>
247
+ </ViewComponent.Row>
248
+ );
249
+ }}
250
+ />
251
+ );
252
+ }
@@ -1,6 +1,8 @@
1
1
  import * as RadixSlider from "@radix-ui/react-slider";
2
2
  import React, { FocusEventHandler, memo, useCallback, useMemo } from "react";
3
+ import { useLabel, useLabelPosition } from "../hooks/useLabel";
3
4
  import { cx } from "../utils/classNames";
5
+ import { InsetLabel } from "./Label";
4
6
 
5
7
  type ColorScheme = "primary" | "secondary";
6
8
 
@@ -20,8 +22,6 @@ interface Props {
20
22
  readOnly?: boolean;
21
23
  }
22
24
 
23
- const labelStyles = `font-sans text-label uppercase text-text-disabled leading-[19px] font-bold text-[60%] pointer-events-none flex items-center z-label`;
24
-
25
25
  const THUMB_WIDTH = 8; // Width of the thumb in pixels
26
26
 
27
27
  const thumbStyle = {
@@ -29,7 +29,6 @@ const thumbStyle = {
29
29
  };
30
30
 
31
31
  export const Slider = memo(function Slider({
32
- id,
33
32
  style,
34
33
  className,
35
34
  value,
@@ -38,16 +37,22 @@ export const Slider = memo(function Slider({
38
37
  max,
39
38
  step,
40
39
  colorScheme,
41
- label,
42
40
  onFocus,
43
41
  onBlur,
44
42
  readOnly = false,
43
+ ...props
45
44
  }: Props) {
46
45
  const arrayValue = useMemo(
47
46
  () => [Math.min(Math.max(value, min), max)],
48
47
  [value, min, max]
49
48
  );
50
49
 
50
+ const { label, fieldId: id } = useLabel({
51
+ label: props.label,
52
+ fieldId: props.id,
53
+ });
54
+ const labelPosition = useLabelPosition();
55
+
51
56
  const handleValueChange = useCallback(
52
57
  (arrayValue: number[]) => {
53
58
  onValueChange(arrayValue[0]);
@@ -90,7 +95,7 @@ export const Slider = memo(function Slider({
90
95
  value={arrayValue}
91
96
  onValueChange={handleValueChange}
92
97
  className={cx(
93
- "flex relative items-center select-none touch-none h-[27px] rounded overflow-hidden w-full",
98
+ "flex relative items-center select-none touch-none h-[27px] rounded overflow-hidden flex-grow max-h-[27px]",
94
99
  className
95
100
  )}
96
101
  style={style}
@@ -98,25 +103,24 @@ export const Slider = memo(function Slider({
98
103
  onBlur={onBlur}
99
104
  disabled={readOnly}
100
105
  >
101
- <RadixSlider.Track className="relative flex-grow h-full rounded overflow-hidden bg-input-background">
102
- {label && (
103
- <label
104
- htmlFor={id}
105
- className={cx(labelStyles, "absolute top-0 bottom-0 right-2")}
106
- >
107
- {label}
108
- </label>
106
+ <RadixSlider.Track className="flex-grow h-full rounded overflow-hidden bg-input-background">
107
+ {label && labelPosition === "inset" && (
108
+ <InsetLabel htmlFor={id}>{label}</InsetLabel>
109
109
  )}
110
110
  <div
111
111
  style={trackFillStyle}
112
112
  className={cx(
113
113
  "absolute inset-0 w-full h-full rounded overflow-hidden",
114
- colorScheme === undefined && "bg-slider-border",
115
114
  colorScheme === "primary" && "bg-primary",
116
115
  colorScheme === "secondary" && "bg-secondary"
117
116
  )}
118
117
  />
119
118
  </RadixSlider.Track>
119
+ {label && labelPosition === "inset" && colorScheme !== undefined && (
120
+ <InsetLabel className="text-white overflow-hidden" style={labelStyle}>
121
+ {label}
122
+ </InsetLabel>
123
+ )}
120
124
  <RadixSlider.Thumb
121
125
  style={thumbStyle}
122
126
  className={cx(
@@ -126,18 +130,6 @@ export const Slider = memo(function Slider({
126
130
  colorScheme === "secondary" && "border-secondary"
127
131
  )}
128
132
  />
129
- {label && (
130
- <label
131
- htmlFor={id}
132
- className={cx(
133
- labelStyles,
134
- "absolute top-0 bottom-0 left-2 right-2 text-white overflow-hidden flex justify-end"
135
- )}
136
- style={labelStyle}
137
- >
138
- {label}
139
- </label>
140
- )}
141
133
  </RadixSlider.Root>
142
134
  );
143
135
  });
@@ -55,6 +55,7 @@ export type TextProps = {
55
55
  variant: Variant;
56
56
  color?: KebabToCamelCase<ThemeColor>;
57
57
  children: ReactNode;
58
+ id?: string;
58
59
  onClick?: () => void;
59
60
  onDoubleClick?: () => void;
60
61
  onKeyDown?: (event: React.KeyboardEvent<HTMLSpanElement>) => void;
@@ -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
  />