@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
@@ -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;
@@ -31,6 +33,7 @@ type Props<T extends string> = {
31
33
  label?: React.ReactNode;
32
34
  onFocus?: FocusEventHandler;
33
35
  onBlur?: FocusEventHandler;
36
+ contentStyle?: React.CSSProperties;
34
37
  } & Pick<SelectProps, "open" | "onOpenChange">;
35
38
 
36
39
  const readOnlyStyle: CSSProperties = {
@@ -38,9 +41,6 @@ const readOnlyStyle: CSSProperties = {
38
41
  textAlign: "left",
39
42
  };
40
43
 
41
- export const labelTextStyles = `font-sans text-label uppercase text-text-disabled leading-[19px] font-bold text-[60%]`;
42
- const labelStyles = `pointer-events-none flex items-center absolute top-0 bottom-0 right-[1.3rem] z-label`;
43
-
44
44
  const scrollButtonStyles = `
45
45
  flex
46
46
  items-center
@@ -51,31 +51,109 @@ const scrollButtonStyles = `
51
51
  hover:text-text
52
52
  `;
53
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
+
54
125
  export const SelectMenu = memoGeneric(function SelectMenu<
55
126
  T extends string = string,
56
127
  >({
57
- id,
58
128
  style,
59
129
  className,
60
- menuItems,
130
+ items,
61
131
  value,
62
132
  onSelect,
63
133
  placeholder,
64
134
  disabled,
65
135
  readOnly,
66
- label,
67
136
  open,
68
137
  onFocus,
69
138
  onBlur,
70
139
  onOpenChange,
140
+ contentStyle,
141
+ ...props
71
142
  }: Props<T>) {
72
- const selectedItem = menuItems.find(
143
+ const selectedItem = items.find(
73
144
  (item): item is RegularMenuItem<T> =>
74
- typeof item !== "string" && item.value === value
145
+ item.type !== "separator" &&
146
+ item.type !== "sectionHeader" &&
147
+ item.value === value
75
148
  );
76
149
  const icon = selectedItem?.icon;
77
150
  const [internalOpen, setInternalOpen] = useState(open);
78
151
 
152
+ const { label, fieldId: id } = useLabel({
153
+ label: props.label,
154
+ fieldId: props.id,
155
+ });
156
+
79
157
  const handleOpenChange = (open: boolean) => {
80
158
  if (onOpenChange) {
81
159
  onOpenChange(open);
@@ -89,92 +167,35 @@ export const SelectMenu = memoGeneric(function SelectMenu<
89
167
  id={id}
90
168
  style={style}
91
169
  contentStyle={readOnlyStyle}
92
- className={cx(`${className} flex-1 focus:z-interactable`)}
170
+ className={cx(`${className} flex-1 focus:z-interactable gap-1.5`)}
93
171
  disabled={disabled}
94
172
  >
95
- {icon && (
96
- <>
97
- {renderIcon(icon)}
98
- <Spacer.Horizontal inline size={6} />
99
- </>
100
- )}
173
+ {icon && renderIcon(icon)}
101
174
  <span className="flex flex-1">{selectedItem?.title ?? value}</span>
102
175
  </Button>
103
176
  ),
104
177
  [icon, id, style, className, disabled, value, selectedItem]
105
178
  );
106
179
 
107
- const trigger = useMemo(
108
- () => (
109
- <Select.SelectTrigger asChild onFocus={onFocus} onBlur={onBlur}>
110
- <Button
111
- id={id}
112
- style={style}
113
- className={cx(
114
- className ?? "w-full",
115
- "flex-1 focus:z-interactable relative",
116
- internalOpen && "ring-2 ring-primary"
117
- )}
118
- disabled={disabled}
119
- >
120
- {icon && (
121
- <>
122
- {renderIcon(icon)}
123
- <Spacer.Horizontal inline size={6} />
124
- </>
125
- )}
126
- <span className="flex flex-1 mr-1.5">
127
- <Select.Value placeholder={placeholder} />
128
- </span>
129
- {label && (
130
- <label htmlFor={id} className={cx(labelStyles, labelTextStyles)}>
131
- {label}
132
- </label>
133
- )}
134
- <DropdownChevronIcon
135
- className={cx(
136
- "transition-transform duration-100",
137
- internalOpen ? "rotate-180" : "rotate-0"
138
- )}
139
- />
140
- </Button>
141
- </Select.SelectTrigger>
142
- ),
143
- [
144
- onFocus,
145
- onBlur,
146
- id,
147
- style,
148
- className,
149
- disabled,
150
- icon,
151
- placeholder,
152
- label,
153
- internalOpen,
154
- ]
155
- );
156
-
157
- if (readOnly) {
158
- return label ? (
159
- <div className="flex flex-col relative">
160
- {readOnlyButton}
161
- <label className={labelStyles} htmlFor={id}>
162
- {label}
163
- </label>
164
- </div>
165
- ) : (
166
- readOnlyButton
167
- );
168
- }
169
-
170
- return (
180
+ const content = (
171
181
  <Select.Root
172
182
  value={value}
173
183
  onValueChange={onSelect}
174
184
  open={open}
175
185
  onOpenChange={handleOpenChange}
176
186
  >
177
- {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
+ />
178
199
  <Select.Portal>
179
200
  <Select.Content
180
201
  className={styles.contentStyle}
@@ -184,6 +205,8 @@ export const SelectMenu = memoGeneric(function SelectMenu<
184
205
  align="end"
185
206
  style={{
186
207
  width: "var(--radix-select-trigger-width)",
208
+ maxHeight: "500px",
209
+ ...contentStyle,
187
210
  }}
188
211
  >
189
212
  <Select.ScrollUpButton className={scrollButtonStyles}>
@@ -192,19 +215,7 @@ export const SelectMenu = memoGeneric(function SelectMenu<
192
215
  />
193
216
  </Select.ScrollUpButton>
194
217
  <Select.Viewport>
195
- {menuItems.map((menuItem) => {
196
- if (typeof menuItem === "string") {
197
- return <Select.Separator className={styles.separatorStyle} />;
198
- }
199
-
200
- const value = menuItem.value ?? "";
201
-
202
- return (
203
- <SelectItem key={value} value={value} icon={menuItem.icon}>
204
- {menuItem.title ?? value}
205
- </SelectItem>
206
- );
207
- })}
218
+ <MenuViewport items={items} Components={Components} />
208
219
  </Select.Viewport>
209
220
  <Select.ScrollDownButton className={scrollButtonStyles}>
210
221
  <ChevronDownIcon />
@@ -213,33 +224,6 @@ export const SelectMenu = memoGeneric(function SelectMenu<
213
224
  </Select.Portal>
214
225
  </Select.Root>
215
226
  );
216
- });
217
227
 
218
- const SelectItem = React.forwardRef(
219
- (
220
- {
221
- children,
222
- icon,
223
- disabled,
224
- ...props
225
- }: Select.SelectItemProps & { icon?: React.ReactNode },
226
- forwardedRef: React.ForwardedRef<HTMLDivElement>
227
- ) => {
228
- return (
229
- <Select.Item
230
- className={styles.itemStyle({ disabled })}
231
- disabled={disabled}
232
- {...props}
233
- ref={forwardedRef}
234
- >
235
- {icon && (
236
- <>
237
- {renderIcon(icon)}
238
- <Spacer.Horizontal size={8} />
239
- </>
240
- )}
241
- <Select.ItemText>{children}</Select.ItemText>
242
- </Select.Item>
243
- );
244
- }
245
- );
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;