@noya-app/noya-designsystem 0.1.48 → 0.1.49

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 (43) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/CHANGELOG.md +8 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +213 -164
  5. package/dist/index.d.ts +213 -164
  6. package/dist/index.js +3583 -3290
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +3617 -3329
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +3 -2
  11. package/src/__tests__/validateDropIndicator.test.ts +4 -4
  12. package/src/components/ActionMenu.tsx +15 -4
  13. package/src/components/BaseToolbar.tsx +2 -2
  14. package/src/components/Checkbox.tsx +2 -2
  15. package/src/components/Chip.tsx +7 -6
  16. package/src/components/Collection.tsx +19 -1
  17. package/src/components/Combobox.tsx +22 -23
  18. package/src/components/ComboboxMenu.tsx +1 -1
  19. package/src/components/DimensionInput.tsx +14 -12
  20. package/src/components/EditableText.tsx +3 -1
  21. package/src/components/FillInputField.tsx +2 -2
  22. package/src/components/Grid.tsx +133 -113
  23. package/src/components/GridView.tsx +36 -18
  24. package/src/components/InputField.tsx +116 -171
  25. package/src/components/Label.tsx +4 -68
  26. package/src/components/LabeledField.tsx +7 -1
  27. package/src/components/List.tsx +103 -44
  28. package/src/components/ListView.tsx +42 -26
  29. package/src/components/MediaThumbnail.tsx +3 -3
  30. package/src/components/Message.tsx +8 -8
  31. package/src/components/NoyaLogo.tsx +41 -0
  32. package/src/components/SegmentedControl.tsx +1 -1
  33. package/src/components/SelectMenu.tsx +9 -4
  34. package/src/components/Slider.tsx +16 -7
  35. package/src/components/Sortable.tsx +62 -25
  36. package/src/components/internal/Menu.tsx +8 -3
  37. package/src/components/internal/SelectItem.tsx +3 -2
  38. package/src/index.css +7 -1
  39. package/src/index.tsx +2 -0
  40. package/src/theme/index.ts +2 -0
  41. package/src/utils/classNames.ts +51 -3
  42. package/src/utils/inputs.ts +21 -0
  43. package/tailwind.config.ts +7 -0
@@ -1,11 +1,11 @@
1
+ import { MultiplayerUser } from "@noya-app/noya-multiplayer-react";
1
2
  import React, { forwardRef, memo, useMemo } from "react";
2
- import { Icons } from "./Icons";
3
- import { Avatar } from "./Avatar";
3
+ import { cssVars, INPUT_HEIGHT } from "../theme";
4
4
  import { cx } from "../utils/classNames";
5
- import { Text } from "./Text";
6
- import { MultiplayerUser } from "@noya-app/noya-multiplayer-react";
7
5
  import { colorFromString } from "../utils/colorFromString";
8
- import { cssVars } from "../theme";
6
+ import { Avatar } from "./Avatar";
7
+ import { Logo } from "./NoyaLogo";
8
+ import { Text } from "./Text";
9
9
 
10
10
  export type MessageProps = {
11
11
  role: "user" | "assistant" | "system" | "data";
@@ -17,7 +17,7 @@ export type MessageProps = {
17
17
 
18
18
  export const Message = memo(
19
19
  forwardRef(function Message(
20
- { role, children, user, avatarSize = 27 }: MessageProps,
20
+ { role, children, user, avatarSize = INPUT_HEIGHT }: MessageProps,
21
21
  ref: React.ForwardedRef<HTMLDivElement>
22
22
  ) {
23
23
  const randomId = crypto.randomUUID();
@@ -42,9 +42,9 @@ export const Message = memo(
42
42
  <Avatar
43
43
  name="Assistant"
44
44
  size={avatarSize}
45
- backgroundColor={cssVars.colors.inputBackground}
45
+ backgroundColor={cssVars.colors.primaryPastel}
46
46
  >
47
- <Icons.PersonIcon fontSize={22} color={cssVars.colors.text} />
47
+ <Logo style={{ width: 15 }} fill={cssVars.colors.primary} />
48
48
  </Avatar>
49
49
  ) : (
50
50
  <Avatar
@@ -0,0 +1,41 @@
1
+ import { cssVars } from "@noya-app/noya-designsystem";
2
+ import React, { forwardRef } from "react";
3
+
4
+ export const Logo = forwardRef(function Logo(
5
+ props: React.ComponentProps<"svg">,
6
+ ref: React.Ref<SVGSVGElement>
7
+ ) {
8
+ const fill = props.fill || cssVars.colors.logoFill;
9
+
10
+ return (
11
+ <svg
12
+ ref={ref}
13
+ viewBox="0 0 24 26"
14
+ fill="none"
15
+ xmlns="http://www.w3.org/2000/svg"
16
+ {...props}
17
+ className="w-6 aspect-[24/26]"
18
+ >
19
+ <path
20
+ d="M6.5 3H2V12H5V6H6.5C7.32843 6 8 6.67157 8 7.5V12H11V7.5C11 5.01472 8.98528 3 6.5 3Z"
21
+ fill={fill}
22
+ />
23
+ <path
24
+ fillRule="evenodd"
25
+ clipRule="evenodd"
26
+ d="M17.5 12C19.9853 12 22 9.98528 22 7.5C22 5.01472 19.9853 3 17.5 3C15.0147 3 13 5.01472 13 7.5C13 9.98528 15.0147 12 17.5 12ZM17.5 9C18.3284 9 19 8.32843 19 7.5C19 6.67157 18.3284 6 17.5 6C16.6716 6 16 6.67157 16 7.5C16 8.32843 16.6716 9 17.5 9Z"
27
+ fill={fill}
28
+ />
29
+ <path
30
+ d="M5 14H2V18.5C2 20.9853 4.01472 23 6.5 23H8V26H11V17H8V20H6.5C5.67157 20 5 19.3284 5 18.5V14Z"
31
+ fill={fill}
32
+ />
33
+ <path
34
+ fillRule="evenodd"
35
+ clipRule="evenodd"
36
+ d="M17.5 23C15.0147 23 13 20.9853 13 18.5C13 16.0147 15.0147 14 17.5 14C19.9853 14 22 16.0147 22 18.5V23H17.5ZM17.5 20C18.3284 20 19 19.3284 19 18.5C19 17.6716 18.3284 17 17.5 17C16.6716 17 16 17.6716 16 18.5C16 19.3284 16.6716 20 17.5 20Z"
37
+ fill={fill}
38
+ />
39
+ </svg>
40
+ );
41
+ });
@@ -141,7 +141,7 @@ export const SegmentedControl = memoGeneric(function SegmentedControl<
141
141
  value={value}
142
142
  onValueChange={handleValueChange}
143
143
  className={cx(
144
- `flex items-stretch flex-auto appearance-none relative outline-none min-h-[27px] rounded bg-input-background py-[2px] px-[2px]`,
144
+ `flex items-stretch flex-auto appearance-none relative outline-none min-h-input-height rounded bg-input-background py-[2px] px-[2px]`,
145
145
  className
146
146
  )}
147
147
  style={style}
@@ -15,6 +15,7 @@ import React, {
15
15
  import { useLabel, useLabelPosition } from "../hooks/useLabel";
16
16
  import { cx } from "../utils/classNames";
17
17
 
18
+ import { getInsetEndStyles } from "../utils/inputs";
18
19
  import { Button } from "./Button";
19
20
  import { renderIcon } from "./Icons";
20
21
  import {
@@ -24,7 +25,7 @@ import {
24
25
  styles,
25
26
  } from "./internal/Menu";
26
27
  import { MenuComponents, MenuViewport } from "./internal/MenuViewport";
27
- import { InsetLabel } from "./Label";
28
+ import { Label } from "./Label";
28
29
 
29
30
  type Props<T extends string> = {
30
31
  id?: string;
@@ -57,6 +58,8 @@ const scrollButtonStyles = `
57
58
  hover:text-text
58
59
  `;
59
60
 
61
+ const insetEndStyles = getInsetEndStyles();
62
+
60
63
  interface SelectMenuTriggerProps {
61
64
  id?: string;
62
65
  style?: React.CSSProperties;
@@ -101,9 +104,11 @@ const SelectMenuTrigger = memoGeneric(function SelectMenuTrigger({
101
104
  >
102
105
  {icon && <span className="pr-1.5">{renderIcon(icon)}</span>}
103
106
  {label && labelPosition === "inset" && (
104
- <InsetLabel className="pr-4" htmlFor={id}>
105
- {label}
106
- </InsetLabel>
107
+ <div className={insetEndStyles}>
108
+ <Label className="pr-4 !text-text-disabled" htmlFor={id}>
109
+ {label}
110
+ </Label>
111
+ </div>
107
112
  )}
108
113
  <span className="flex-1 flex">
109
114
  <Select.Value placeholder={placeholder} />
@@ -2,7 +2,8 @@ import * as RadixSlider from "@radix-ui/react-slider";
2
2
  import React, { FocusEventHandler, memo, useCallback, useMemo } from "react";
3
3
  import { useLabel, useLabelPosition } from "../hooks/useLabel";
4
4
  import { cx } from "../utils/classNames";
5
- import { InsetLabel } from "./Label";
5
+ import { getInsetEndStyles } from "../utils/inputs";
6
+ import { Label } from "./Label";
6
7
 
7
8
  type ColorScheme = "primary" | "secondary";
8
9
 
@@ -28,6 +29,8 @@ const thumbStyle = {
28
29
  width: THUMB_WIDTH,
29
30
  };
30
31
 
32
+ const insetEndStyles = getInsetEndStyles();
33
+
31
34
  export const Slider = memo(function Slider({
32
35
  style,
33
36
  className,
@@ -95,7 +98,7 @@ export const Slider = memo(function Slider({
95
98
  value={arrayValue}
96
99
  onValueChange={handleValueChange}
97
100
  className={cx(
98
- "flex relative items-center select-none touch-none h-[27px] rounded overflow-hidden flex-grow max-h-[27px]",
101
+ "flex relative items-center select-none touch-none h-input-height rounded overflow-hidden flex-grow max-h-input-height",
99
102
  className
100
103
  )}
101
104
  style={style}
@@ -105,7 +108,11 @@ export const Slider = memo(function Slider({
105
108
  >
106
109
  <RadixSlider.Track className="flex-grow h-full rounded overflow-hidden bg-input-background">
107
110
  {label && labelPosition === "inset" && (
108
- <InsetLabel htmlFor={id}>{label}</InsetLabel>
111
+ <div className={insetEndStyles}>
112
+ <Label htmlFor={id} className="!text-text-disabled">
113
+ {label}
114
+ </Label>
115
+ </div>
109
116
  )}
110
117
  <div
111
118
  style={trackFillStyle}
@@ -117,14 +124,16 @@ export const Slider = memo(function Slider({
117
124
  />
118
125
  </RadixSlider.Track>
119
126
  {label && labelPosition === "inset" && colorScheme !== undefined && (
120
- <InsetLabel className="text-white overflow-hidden" style={labelStyle}>
121
- {label}
122
- </InsetLabel>
127
+ <div className={insetEndStyles}>
128
+ <Label className="text-white overflow-hidden" style={labelStyle}>
129
+ {label}
130
+ </Label>
131
+ </div>
123
132
  )}
124
133
  <RadixSlider.Thumb
125
134
  style={thumbStyle}
126
135
  className={cx(
127
- "block h-[27px] rounded border border-solid bg-slider-thumb-background transition-colors focus:border-primary focus:border-2 outline-none",
136
+ "block h-input-height rounded border border-solid bg-slider-thumb-background transition-colors focus:border-primary focus:border-2 outline-none",
128
137
  colorScheme === undefined && "border-slider-border",
129
138
  colorScheme === "primary" && "border-primary",
130
139
  colorScheme === "secondary" && "border-secondary"
@@ -25,11 +25,11 @@ export type RelativeDropPosition = "above" | "below" | "inside";
25
25
 
26
26
  export type DropValidator = (
27
27
  sourceIndex: number,
28
- destinationIndex: number,
28
+ targetIndex: number,
29
29
  position: RelativeDropPosition
30
30
  ) => boolean;
31
31
 
32
- export const normalizeListDestinationIndex = (
32
+ export const normalizeListTargetIndex = (
33
33
  index: number,
34
34
  position: "above" | "below"
35
35
  ): number => {
@@ -38,12 +38,12 @@ export const normalizeListDestinationIndex = (
38
38
 
39
39
  export const defaultAcceptsDrop: DropValidator = (
40
40
  sourceIndex,
41
- destinationIndex,
41
+ targetIndex,
42
42
  position
43
43
  ) => {
44
44
  if (position === "inside") return false;
45
45
 
46
- const normalized = normalizeListDestinationIndex(destinationIndex, position);
46
+ const normalized = normalizeListTargetIndex(targetIndex, position);
47
47
 
48
48
  if (sourceIndex === normalized || sourceIndex + 1 === normalized) {
49
49
  return false;
@@ -52,18 +52,22 @@ export const defaultAcceptsDrop: DropValidator = (
52
52
  return true;
53
53
  };
54
54
 
55
- const SortableItemContext = React.createContext<{
55
+ export type SortableItemContextProps = {
56
56
  keys: UniqueIdentifier[];
57
57
  acceptsDrop: DropValidator;
58
58
  axis: "x" | "y";
59
59
  position: { x: number; y: number };
60
60
  setActivatorEvent: (event: PointerEvent) => void;
61
- }>({
61
+ getDropTargetParentIndex?: (index: number) => number | undefined;
62
+ };
63
+
64
+ const SortableItemContext = React.createContext<SortableItemContextProps>({
62
65
  keys: [],
63
66
  acceptsDrop: defaultAcceptsDrop,
64
67
  axis: "y",
65
68
  position: { x: 0, y: 0 },
66
69
  setActivatorEvent: () => {},
70
+ getDropTargetParentIndex: undefined,
67
71
  });
68
72
 
69
73
  type ValidateDropIndicatorParams = {
@@ -135,8 +139,14 @@ function SortableItem<T extends HTMLElement>({
135
139
  disabled,
136
140
  children,
137
141
  }: ItemProps<T>) {
138
- const { keys, position, acceptsDrop, setActivatorEvent, axis } =
139
- React.useContext(SortableItemContext);
142
+ const {
143
+ keys,
144
+ position,
145
+ acceptsDrop,
146
+ setActivatorEvent,
147
+ axis,
148
+ getDropTargetParentIndex,
149
+ } = React.useContext(SortableItemContext);
140
150
  const sortable = useSortable({ id, disabled });
141
151
  const { activatorEvent } = useDndContext();
142
152
 
@@ -147,6 +157,7 @@ function SortableItem<T extends HTMLElement>({
147
157
  setNodeRef,
148
158
  isDragging,
149
159
  index,
160
+ activeIndex,
150
161
  overIndex,
151
162
  over,
152
163
  } = sortable;
@@ -161,23 +172,40 @@ function SortableItem<T extends HTMLElement>({
161
172
  const offsetTop = eventY + position.y;
162
173
 
163
174
  const ref = React.useCallback((node: T) => setNodeRef(node), [setNodeRef]);
175
+ const parentIndex =
176
+ overIndex === -1 ? undefined : getDropTargetParentIndex?.(overIndex);
177
+ const isValidParent =
178
+ parentIndex === undefined || parentIndex === -1
179
+ ? false
180
+ : acceptsDrop(activeIndex, parentIndex, "inside");
181
+ const overIdAcceptsDrop =
182
+ overIndex === -1
183
+ ? undefined
184
+ : acceptsDrop(activeIndex, overIndex, "inside");
185
+
186
+ const relativeDropPosition =
187
+ index >= 0 && index === overIndex && !isDragging && active && over
188
+ ? validateDropIndicator({
189
+ acceptsDrop,
190
+ keys,
191
+ activeId: active.id,
192
+ overId: over.id,
193
+ offsetStart: axis === "x" ? offsetLeft : offsetTop,
194
+ elementStart: axis === "x" ? over.rect.left : over.rect.top,
195
+ elementSize: axis === "x" ? over.rect.width : over.rect.height,
196
+ })
197
+ : undefined;
198
+
199
+ const showDragInsideIndicatorOnParent =
200
+ !overIdAcceptsDrop && isValidParent && parentIndex === index;
164
201
 
165
202
  return children({
166
203
  ref,
167
204
  ...attributes,
168
205
  ...listeners,
169
206
  relativeDropPosition:
170
- index >= 0 && index === overIndex && !isDragging && active && over
171
- ? validateDropIndicator({
172
- acceptsDrop,
173
- keys,
174
- activeId: active.id,
175
- overId: over.id,
176
- offsetStart: axis === "x" ? offsetLeft : offsetTop,
177
- elementStart: axis === "x" ? over.rect.left : over.rect.top,
178
- elementSize: axis === "x" ? over.rect.width : over.rect.height,
179
- })
180
- : undefined,
207
+ relativeDropPosition ??
208
+ (showDragInsideIndicatorOnParent ? "inside" : undefined),
181
209
  });
182
210
  }
183
211
 
@@ -191,11 +219,12 @@ interface RootProps {
191
219
  renderOverlay?: (index: number) => React.ReactNode;
192
220
  onMoveItem?: (
193
221
  sourceIndex: number,
194
- destinationIndex: number,
222
+ targetIndex: number,
195
223
  position: RelativeDropPosition
196
224
  ) => void;
197
- acceptsDrop?: DropValidator;
198
- axis?: "x" | "y";
225
+ acceptsDrop?: SortableItemContextProps["acceptsDrop"];
226
+ axis?: SortableItemContextProps["axis"];
227
+ getDropTargetParentIndex?: SortableItemContextProps["getDropTargetParentIndex"];
199
228
  }
200
229
 
201
230
  function SortableRoot({
@@ -205,6 +234,7 @@ function SortableRoot({
205
234
  renderOverlay,
206
235
  acceptsDrop = defaultAcceptsDrop,
207
236
  axis = "y",
237
+ getDropTargetParentIndex,
208
238
  }: RootProps) {
209
239
  const sensors = useSensors(
210
240
  useSensor(PointerSensor, {
@@ -242,7 +272,6 @@ function SortableRoot({
242
272
  const { active, over } = event;
243
273
 
244
274
  setActiveIndex(undefined);
245
-
246
275
  if (over && active.id !== over.id) {
247
276
  const oldIndex = keys.findIndex((id) => id === active.id);
248
277
  const newIndex = keys.findIndex((id) => id === over.id);
@@ -281,14 +310,22 @@ function SortableRoot({
281
310
  return (
282
311
  <SortableItemContext.Provider
283
312
  value={React.useMemo(
284
- () => ({
313
+ (): SortableItemContextProps => ({
285
314
  keys,
286
315
  acceptsDrop,
287
316
  position,
288
317
  setActivatorEvent,
289
318
  axis,
319
+ getDropTargetParentIndex,
290
320
  }),
291
- [acceptsDrop, axis, keys, position, setActivatorEvent]
321
+ [
322
+ acceptsDrop,
323
+ axis,
324
+ getDropTargetParentIndex,
325
+ keys,
326
+ position,
327
+ setActivatorEvent,
328
+ ]
292
329
  )}
293
330
  >
294
331
  <DndContext
@@ -157,12 +157,12 @@ export const getMenuItemKey = <T extends string>(
157
157
  }
158
158
  };
159
159
 
160
- export const CHECKBOX_WIDTH = 16;
160
+ export const CHECKBOX_WIDTH = 15;
161
161
  export const CHECKBOX_RIGHT_INSET = 8;
162
162
  export const CHECKBOX_INDENT_WIDTH = 6;
163
163
 
164
164
  export const styles = {
165
- separatorStyle: "h-px bg-divider mx-4 my-1",
165
+ separatorStyle: "h-px bg-divider mx-3 my-1",
166
166
  selectedItemStyle:
167
167
  "focus:outline-none focus:text-white focus:bg-primary focus:kbd:text-white",
168
168
  testSelectedItemStyle: "outline-none text-white bg-primary kbd:text-white",
@@ -175,7 +175,12 @@ export const styles = {
175
175
  font-sans text-button font-medium
176
176
  ${disabled ? "text-text-disabled" : ""}
177
177
  `,
178
- itemIndicatorStyle: "flex items-center relative -left-2.5 -mr-2",
178
+ itemIndicator: {
179
+ className: "flex items-center relative -left-2.5",
180
+ style: {
181
+ marginRight: -CHECKBOX_RIGHT_INSET,
182
+ },
183
+ },
179
184
  contentStyle:
180
185
  "rounded bg-popover-background text-text shadow-[0_2px_4px_rgba(0,0,0,0.2),_0_0_12px_rgba(0,0,0,0.1)] z-menu py-1",
181
186
  };
@@ -65,8 +65,9 @@ export const SelectItem = React.forwardRef(
65
65
  testSelected && styles.testSelectedItemStyle
66
66
  )}
67
67
  >
68
+ <Spacer.Horizontal size={CHECKBOX_INDENT_WIDTH} />
68
69
  {Components.ItemIndicator && (
69
- <Components.ItemIndicator className={styles.itemIndicatorStyle}>
70
+ <Components.ItemIndicator {...styles.itemIndicator}>
70
71
  <CheckIcon />
71
72
  </Components.ItemIndicator>
72
73
  )}
@@ -107,7 +108,7 @@ export const SelectItem = React.forwardRef(
107
108
  <div className="flex flex-1 items-center">
108
109
  {indented && <Spacer.Horizontal size={CHECKBOX_INDENT_WIDTH} />}
109
110
  {checked ? (
110
- <div className={styles.itemIndicatorStyle}>
111
+ <div {...styles.itemIndicator}>
111
112
  <CheckIcon />
112
113
  </div>
113
114
  ) : indented ? (
package/src/index.css CHANGED
@@ -21,7 +21,6 @@
21
21
  --secondary-light: rgb(0, 160, 129);
22
22
  --secondary-pastel: rgb(205, 238, 231);
23
23
  --secondary-bright: #0ab557;
24
- --input-background: rgb(240, 242, 246);
25
24
  --input-background-light: rgb(243, 245, 249);
26
25
  --code-background: rgb(250, 250, 250);
27
26
  --code-background-dark: #435080;
@@ -37,6 +36,9 @@
37
36
  --popover-divider: transparent;
38
37
  --listview-raised-background: rgba(0, 0, 0, 0.03);
39
38
  --listview-editing-background: #fff;
39
+ --input-background: rgb(240, 242, 246);
40
+ --list-view-hover-background: rgb(244, 246, 250);
41
+ --list-view-thumbnail-background: var(--input-background);
40
42
  --slider-thumb-background: white;
41
43
  --slider-border: #9698ac;
42
44
  --mask: rgb(12, 193, 67);
@@ -78,6 +80,8 @@
78
80
  --chip-error-shadow: rgb(255, 219, 219);
79
81
  --chip-default-shadow: rgb(0, 0, 0);
80
82
  --floating-button: rgb(248, 248, 250);
83
+ --block-border: rgb(184, 201, 218);
84
+ --block-highlight: rgb(255, 165, 0);
81
85
  }
82
86
 
83
87
  [data-theme="dark"] {
@@ -105,6 +109,8 @@
105
109
  --popover-divider: rgba(255, 255, 255, 0.08);
106
110
  --listview-raised-background: rgba(181, 178, 255, 0.1);
107
111
  --listview-editing-background: #000;
112
+ --list-view-hover-background: var(--input-background-light);
113
+ --list-view-thumbnail-background: var(--input-background);
108
114
  --slider-thumb-background: var(--input-background-light);
109
115
  --slider-border: var(--divider);
110
116
  --mask: rgb(102, 187, 106);
package/src/index.tsx CHANGED
@@ -1,4 +1,5 @@
1
1
  // Components
2
+ export * from "./components/ActionMenu";
2
3
  export * from "./components/ActivityIndicator";
3
4
  export * from "./components/AnimatePresence";
4
5
  export * from "./components/Avatar";
@@ -42,6 +43,7 @@ export * from "./components/List";
42
43
  export * from "./components/ListView";
43
44
  export * from "./components/MediaThumbnail";
44
45
  export * from "./components/Message";
46
+ export * from "./components/NoyaLogo";
45
47
  export * from "./components/Popover";
46
48
  export * from "./components/Progress";
47
49
  export * from "./components/ScrollArea";
@@ -6,3 +6,5 @@ export type Theme = (typeof tailwindConfig.theme)["extend"];
6
6
  export type ThemeColor = keyof Theme["colors"];
7
7
 
8
8
  export const cssVars = convertKebabToCamelCase(tailwindConfig.theme.extend);
9
+
10
+ export const INPUT_HEIGHT = 27;
@@ -1,5 +1,53 @@
1
- export function cx(
2
- ...args: Array<string | number | boolean | null | undefined>
3
- ): string {
1
+ import tailwindConfig from "../../tailwind.config";
2
+
3
+ type ClassNameItem = string | number | BigInt | boolean | null | undefined;
4
+ type Category = "text" | "flex";
5
+ type Options = {
6
+ categories?: Category[];
7
+ };
8
+
9
+ const textKeys = Object.keys(tailwindConfig.theme.extend.fontSize).map(
10
+ (key) => `text-${key}`
11
+ );
12
+ const textKeysSet = new Set(textKeys);
13
+ const keyMap: Record<Category, Set<string>> = {
14
+ text: textKeysSet,
15
+ flex: new Set(["flex-auto", "flex-none", "flex-[0_0_auto]", "flex-1"]),
16
+ };
17
+
18
+ export function cx(...args: ClassNameItem[]): string {
4
19
  return args.filter(Boolean).join(" ").trim();
5
20
  }
21
+
22
+ const filterLastClassNameInCategory = ({
23
+ classNames,
24
+ categoryKeys,
25
+ }: {
26
+ classNames: string[];
27
+ categoryKeys: Set<string>;
28
+ }) => {
29
+ const lastClassInGroup = classNames.findLast((key) => categoryKeys.has(key));
30
+ return classNames.flatMap((className) => {
31
+ if (categoryKeys.has(className)) {
32
+ return lastClassInGroup === className ? [className] : [];
33
+ }
34
+ return [className];
35
+ });
36
+ };
37
+
38
+ export function mergeConflictingClassNames(
39
+ classNames: ClassNameItem | ClassNameItem[],
40
+ options?: Options
41
+ ): string {
42
+ const classes = Array.isArray(classNames)
43
+ ? cx(...classNames)
44
+ : cx(classNames);
45
+ let classNamesList = classes.split(/\s+/);
46
+ options?.categories?.forEach((category) => {
47
+ classNamesList = filterLastClassNameInCategory({
48
+ classNames: classNamesList,
49
+ categoryKeys: keyMap[category],
50
+ });
51
+ });
52
+ return classNamesList.join(" ");
53
+ }
@@ -0,0 +1,21 @@
1
+ import { cx } from "./classNames";
2
+
3
+ export type InputSize = "small" | "medium" | "large";
4
+
5
+ const insetEndBaseStyles = "absolute inset-y-0 flex items-center z-label";
6
+
7
+ export const getInsetEndStyles = (size: InputSize = "medium") => {
8
+ return cx(
9
+ insetEndBaseStyles,
10
+ size === "large"
11
+ ? "right-2"
12
+ : size === "medium"
13
+ ? "right-1.5"
14
+ : size === "small"
15
+ ? "right-0.5"
16
+ : "",
17
+ size === "small" ? "min-h-[19px]" : "min-h-input-height"
18
+ );
19
+ };
20
+
21
+ export const startBaseStyles = "absolute left-1.5 inset-y-0 flex items-center";
@@ -1,3 +1,4 @@
1
+ import containerQueries from "@tailwindcss/container-queries";
1
2
  import type { Config } from "tailwindcss";
2
3
 
3
4
  const config = {
@@ -29,6 +30,9 @@ const config = {
29
30
  "secondary-bright": "var(--secondary-bright)",
30
31
  "input-background": "var(--input-background)",
31
32
  "input-background-light": "var(--input-background-light)",
33
+ "list-view-hover-background": "var(--list-view-hover-background)",
34
+ "list-view-thumbnail-background":
35
+ "var(--list-view-thumbnail-background)",
32
36
  "code-background": "var(--code-background)",
33
37
  "code-background-dark": "var(--code-background-dark)",
34
38
  "selected-background": "var(--selected-background)",
@@ -74,6 +78,8 @@ const config = {
74
78
  "chip-error-shadow": "var(--chip-error-shadow)",
75
79
  "chip-default-shadow": "var(--chip-default-shadow)",
76
80
  "floating-button": "var(--floating-button)",
81
+ "block-border": "var(--block-border)",
82
+ "block-highlight": "var(--block-highlight)",
77
83
  },
78
84
  fontFamily: {
79
85
  sans: [
@@ -235,6 +241,7 @@ const config = {
235
241
  "gap-toolbar-separator",
236
242
  "-mr-1",
237
243
  ],
244
+ plugins: [containerQueries],
238
245
  } satisfies Config;
239
246
 
240
247
  export default config;