@noya-app/noya-designsystem 0.1.38 → 0.1.40

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 (50) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/CHANGELOG.md +16 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +416 -321
  5. package/dist/index.d.ts +416 -321
  6. package/dist/index.js +9555 -11325
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +5057 -6832
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +4 -4
  11. package/src/components/ActivityIndicator.tsx +13 -4
  12. package/src/components/AnimatePresence.tsx +261 -0
  13. package/src/components/Avatar.tsx +202 -80
  14. package/src/components/Button.tsx +10 -6
  15. package/src/components/Checkbox.tsx +10 -9
  16. package/src/components/Chip.tsx +88 -132
  17. package/src/components/{InputFieldWithCompletions.tsx → Combobox.tsx} +10 -3
  18. package/src/components/ContextMenu.tsx +9 -5
  19. package/src/components/Dialog.tsx +20 -23
  20. package/src/components/Divider.tsx +15 -7
  21. package/src/components/DraggableMenuButton.tsx +9 -5
  22. package/src/components/DropdownMenu.tsx +8 -5
  23. package/src/components/FillInputField.tsx +5 -1
  24. package/src/components/FillPreviewBackground.tsx +1 -5
  25. package/src/components/GridView.tsx +71 -46
  26. package/src/components/InputField.tsx +140 -75
  27. package/src/components/InspectorPrimitives.tsx +17 -11
  28. package/src/components/Label.tsx +5 -1
  29. package/src/components/ListView.tsx +28 -15
  30. package/src/components/Progress.tsx +10 -7
  31. package/src/components/SelectMenu.tsx +20 -8
  32. package/src/components/Slider.tsx +70 -8
  33. package/src/components/Sortable.tsx +3 -3
  34. package/src/components/Switch.tsx +19 -2
  35. package/src/components/Text.tsx +5 -1
  36. package/src/components/TextArea.tsx +5 -1
  37. package/src/components/TreeView.tsx +4 -10
  38. package/src/components/UserPointer.tsx +170 -0
  39. package/src/components/WorkspaceLayout.tsx +5 -0
  40. package/src/components/internal/Menu.tsx +14 -5
  41. package/src/components/internal/TextInput.tsx +13 -0
  42. package/src/contexts/DialogContext.tsx +30 -18
  43. package/src/hooks/useTheme.ts +50 -0
  44. package/src/index.css +16 -13
  45. package/src/index.tsx +14 -26
  46. package/src/utils/classNames.ts +5 -0
  47. package/src/utils/colorFromString.ts +44 -0
  48. package/tailwind.config.ts +16 -13
  49. package/src/hooks/useDarkMode.ts +0 -14
  50. package/src/utils/tailwind.ts +0 -9
@@ -5,9 +5,9 @@ import React, {
5
5
  memo,
6
6
  ReactNode,
7
7
  } from "react";
8
- import { cn } from "../utils/tailwind";
9
8
  import { Spacer } from "./Spacer";
10
9
  import { textStyles } from "./Text";
10
+ import { cx } from "../utils/classNames";
11
11
 
12
12
  export const Section = forwardRef<
13
13
  HTMLDivElement,
@@ -15,7 +15,7 @@ export const Section = forwardRef<
15
15
  >(({ className, ...props }, ref) => (
16
16
  <div
17
17
  ref={ref}
18
- className={cn("flex-none flex flex-col", className)}
18
+ className={cx(`flex-none flex flex-col `, className)}
19
19
  {...props}
20
20
  />
21
21
  ));
@@ -24,7 +24,7 @@ export const SectionHeader = forwardRef<
24
24
  HTMLDivElement,
25
25
  React.HTMLAttributes<HTMLDivElement>
26
26
  >(({ className, ...props }, ref) => (
27
- <div ref={ref} className={cn("flex items-center", className)} {...props} />
27
+ <div ref={ref} className={cx(`flex items-center `, className)} {...props} />
28
28
  ));
29
29
 
30
30
  type TitleTextStyle = "small" | "heading5" | "heading4" | "heading3";
@@ -37,7 +37,10 @@ export const Title = forwardRef<
37
37
  >(({ className, $textStyle, ...props }, ref) => (
38
38
  <div
39
39
  ref={ref}
40
- className={`flex select-none ${$textStyle ? `${textStyles[$textStyle]} text-text` : "font-sans text-label uppercase text-text-muted font-bold"} ${className ?? ""}`}
40
+ className={cx(
41
+ `flex select-none ${$textStyle ? `${textStyles[$textStyle]} text-text` : "font-sans text-label uppercase text-text-muted font-bold"} `,
42
+ className
43
+ )}
41
44
  {...props}
42
45
  />
43
46
  ));
@@ -48,7 +51,7 @@ export const Row = forwardRef<
48
51
  >(({ className, ...props }, ref) => (
49
52
  <div
50
53
  ref={ref}
51
- className={cn("flex-1 flex flex-row items-center", className)}
54
+ className={cx(`flex-1 flex flex-row items-center `, className)}
52
55
  {...props}
53
56
  />
54
57
  ));
@@ -59,7 +62,7 @@ export const Column = forwardRef<
59
62
  >(({ className, ...props }, ref) => (
60
63
  <div
61
64
  ref={ref}
62
- className={cn("flex-1 flex flex-col gap-1", className)}
65
+ className={cx(`flex-1 flex flex-col gap-1 `, className)}
63
66
  {...props}
64
67
  />
65
68
  ));
@@ -71,7 +74,7 @@ export const Checkbox = forwardRef<
71
74
  <input
72
75
  ref={ref}
73
76
  type="checkbox"
74
- className={cn("m-0 focus:z-interactable", className)}
77
+ className={cx(`m-0 focus:z-interactable `, className)}
75
78
  {...props}
76
79
  />
77
80
  ));
@@ -82,7 +85,7 @@ export const Text = forwardRef<
82
85
  >(({ className, ...props }, ref) => (
83
86
  <span
84
87
  ref={ref}
85
- className={cn("font-sans text-heading5 font-normal", className)}
88
+ className={cx(`font-sans text-heading5 font-normal `, className)}
86
89
  {...props}
87
90
  />
88
91
  ));
@@ -101,7 +104,7 @@ export const HorizontalSeparator = () => (
101
104
  // >(({ className, ...props }, ref) => (
102
105
  // <span
103
106
  // ref={ref}
104
- // className={`font-sans text-heading5 font-normal text-text-muted -mb-[6px] ${className ?? ""}`}
107
+ // className={`font-sans text-heading5 font-normal text-text-muted -mb-[6px] `, className}
105
108
  // {...props}
106
109
  // />
107
110
  // ));
@@ -119,7 +122,10 @@ export const RowLabel = forwardRef(function RowLabel(
119
122
  return (
120
123
  <label
121
124
  ref={ref}
122
- className={`font-sans text-label uppercase flex items-center leading-[19px] font-bold text-text-subtle z-label ${$textStyle ? textStyles[$textStyle] : ""} ${className ?? ""}`}
125
+ className={cx(
126
+ `font-sans text-label uppercase flex items-center leading-[19px] font-bold text-text-subtle z-label ${$textStyle && textStyles[$textStyle]} `,
127
+ className
128
+ )}
123
129
  {...props}
124
130
  />
125
131
  );
@@ -143,7 +149,7 @@ export const LabeledRow = memo(function LabeledRow({
143
149
  className,
144
150
  }: LabeledRowProps) {
145
151
  return (
146
- <Row id={id} className={className ?? ""}>
152
+ <Row id={id} className={className}>
147
153
  <Column>
148
154
  <RowLabel $textStyle={labelTextStyle}>
149
155
  {label}
@@ -1,4 +1,5 @@
1
1
  import React, { forwardRef, LabelHTMLAttributes, memo } from "react";
2
+ import { cx } from "../utils/classNames";
2
3
 
3
4
  interface LabelProps extends LabelHTMLAttributes<HTMLLabelElement> {}
4
5
 
@@ -10,7 +11,10 @@ export const Label = memo(
10
11
  return (
11
12
  <label
12
13
  ref={ref}
13
- className={`font-sans text-label uppercase flex items-center leading-[19px] select-none font-bold text-text-muted z-label ${className ?? ""}`}
14
+ className={cx(
15
+ `font-sans text-label uppercase flex items-center leading-[19px] select-none font-bold text-text-muted z-label `,
16
+ className
17
+ )}
14
18
  {...props}
15
19
  />
16
20
  );
@@ -1,5 +1,6 @@
1
1
  import { Size } from "@noya-app/noya-geometry";
2
2
  import { range } from "@noya-app/noya-utils";
3
+ import { forwardRefGeneric, memoGeneric } from "@noya-app/react-utils";
3
4
  import { composeRefs } from "@radix-ui/react-compose-refs";
4
5
  import React, {
5
6
  Children,
@@ -25,6 +26,7 @@ import { ListChildComponentProps, VariableSizeList } from "react-window";
25
26
  import { mergeEventHandlers } from "../hooks/mergeEventHandlers";
26
27
  import { useHover } from "../hooks/useHover";
27
28
  import { cssVars } from "../theme";
29
+ import { cx } from "../utils/classNames";
28
30
  import { ContextMenu } from "./ContextMenu";
29
31
  import { InputField, InputFieldInputProps } from "./InputField";
30
32
  import { MenuItem } from "./internal/Menu";
@@ -105,7 +107,10 @@ const ListViewRowTitle = ({
105
107
  children: React.ReactNode;
106
108
  } & HTMLAttributes<HTMLSpanElement>) => (
107
109
  <span
108
- className={`flex-1 overflow-hidden text-ellipsis whitespace-pre ${className ?? ""}`}
110
+ className={cx(
111
+ `flex-1 overflow-hidden text-ellipsis whitespace-pre `,
112
+ className
113
+ )}
109
114
  {...props}
110
115
  >
111
116
  {children}
@@ -123,7 +128,7 @@ const ListViewEditableRowTitleElement = forwardRef(
123
128
  ) => (
124
129
  <InputField.Input
125
130
  ref={forwardedRef}
126
- className={`bg-listview-editing-background ${className ?? ""}`}
131
+ className={cx(`bg-listview-editing-background `, className)}
127
132
  {...props}
128
133
  />
129
134
  )
@@ -226,11 +231,14 @@ const RowContainer = forwardRef<
226
231
  return (
227
232
  <div
228
233
  ref={ref}
229
- className={`${
230
- $isSectionHeader && $sectionHeaderVariant === "label"
231
- ? "font-sans text-label font-medium"
232
- : "font-sans text-heading5 font-normal"
233
- } ${className ?? ""}`}
234
+ className={cx(
235
+ `${
236
+ $isSectionHeader && $sectionHeaderVariant === "label"
237
+ ? "font-sans text-label font-medium"
238
+ : "font-sans text-heading5 font-normal"
239
+ } `,
240
+ className
241
+ )}
234
242
  style={{
235
243
  ...($isSectionHeader && { fontWeight: 500 }),
236
244
  gap: $gap,
@@ -418,7 +426,7 @@ interface ListViewRowProps<MenuItemType extends string = string> {
418
426
  }) => CSSProperties);
419
427
  }
420
428
 
421
- const ListViewRow = forwardRef(function ListViewRow<
429
+ const ListViewRow = forwardRefGeneric(function ListViewRow<
422
430
  MenuItemType extends string,
423
431
  >(
424
432
  {
@@ -634,7 +642,7 @@ export interface IVirtualizedList {
634
642
  scrollToIndex(index: number): void;
635
643
  }
636
644
 
637
- const VirtualizedListInner = forwardRef(function VirtualizedListInner<T>(
645
+ const VirtualizedListInner = forwardRefGeneric(function VirtualizedListInner<T>(
638
646
  {
639
647
  size,
640
648
  scrollElement,
@@ -781,7 +789,7 @@ type ListViewRootProps = {
781
789
  colorScheme?: ListColorScheme;
782
790
  };
783
791
 
784
- const ListViewRootInner = forwardRef(function ListViewRootInner<T>(
792
+ const ListViewRootInner = forwardRefGeneric(function ListViewRootInner<T>(
785
793
  {
786
794
  id,
787
795
  className,
@@ -1009,7 +1017,10 @@ const ListViewRootInner = forwardRef(function ListViewRootInner<T>(
1009
1017
  <ListViewDraggingContext.Provider value={draggingContextValue}>
1010
1018
  <div
1011
1019
  id={id}
1012
- className={`flex flex-col text-text-muted ${scrollable ? "flex-1" : "flex-none"} ${className ?? ""}`}
1020
+ className={cx(
1021
+ `flex flex-col text-text-muted ${scrollable ? "flex-1" : "flex-none"} `,
1022
+ className
1023
+ )}
1013
1024
  style={{ ...gapStyle, ...style }}
1014
1025
  {...{
1015
1026
  [pressEventName]: handleClick,
@@ -1037,7 +1048,7 @@ const ListViewRootInner = forwardRef(function ListViewRootInner<T>(
1037
1048
  );
1038
1049
  });
1039
1050
 
1040
- const ListViewRoot = memo(ListViewRootInner) as typeof ListViewRootInner;
1051
+ const ListViewRoot = memoGeneric(ListViewRootInner) as typeof ListViewRootInner;
1041
1052
 
1042
1053
  const ChildrenListViewInner = forwardRef(function ChildrenListViewInner(
1043
1054
  { children, ...rest }: ChildrenProps & ListViewRootProps,
@@ -1068,7 +1079,9 @@ const ChildrenListViewInner = forwardRef(function ChildrenListViewInner(
1068
1079
 
1069
1080
  const ChildrenListView = memo(ChildrenListViewInner);
1070
1081
 
1071
- const SimpleListViewInner = forwardRef(function SimpleListViewInner<T = any>(
1082
+ const SimpleListViewInner = forwardRefGeneric(function SimpleListViewInner<
1083
+ T = any,
1084
+ >(
1072
1085
  props: (ChildrenProps | RenderProps<T>) & ListViewRootProps,
1073
1086
  forwardedRef: ForwardedRef<IVirtualizedList>
1074
1087
  ) {
@@ -1082,12 +1095,12 @@ const SimpleListViewInner = forwardRef(function SimpleListViewInner<T = any>(
1082
1095
  /**
1083
1096
  * A ListView can be created either with `children` or render props
1084
1097
  */
1085
- const SimpleListView = memo(SimpleListViewInner);
1098
+ const SimpleListView = memoGeneric(SimpleListViewInner);
1086
1099
 
1087
1100
  export namespace ListView {
1088
1101
  export const RowTitle = memo(ListViewRowTitle);
1089
1102
  export const EditableRowTitle = memo(ListViewEditableRowTitle);
1090
- export const Row = memo(ListViewRow);
1103
+ export const Row = memoGeneric(ListViewRow);
1091
1104
  export const Root = SimpleListView;
1092
1105
  export const RowContext = ListRowContext;
1093
1106
  export type ClickInfo = ListViewClickInfo;
@@ -1,7 +1,7 @@
1
1
  import { clamp } from "@noya-app/noya-utils";
2
2
  import * as ProgressPrimitive from "@radix-ui/react-progress";
3
3
  import React, { useMemo } from "react";
4
- import { cn } from "../utils/tailwind";
4
+ import { cx } from "../utils/classNames";
5
5
 
6
6
  type ProgressVariant = "normal" | "warning" | "primary" | "secondary";
7
7
 
@@ -15,20 +15,23 @@ export function Progress({
15
15
  className?: string;
16
16
  }) {
17
17
  const clampedValue = clamp(value, 0, 100);
18
- const transformStyles = useMemo(() => ({
19
- transform: `translateX(-${100 - clampedValue}%)`,
20
- }), [clampedValue]);
18
+ const transformStyles = useMemo(
19
+ () => ({
20
+ transform: `translateX(-${100 - clampedValue}%)`,
21
+ }),
22
+ [clampedValue]
23
+ );
21
24
 
22
25
  return (
23
26
  <ProgressPrimitive.Root
24
- className={`relative hidden bg-input-background h-[5px] ${className ?? ""}`}
27
+ className={cx(`relative hidden bg-input-background h-[5px] `, className)}
25
28
  style={{ transform: "translateZ(0)" }}
26
29
  value={clampedValue}
27
30
  >
28
31
  <ProgressPrimitive.Indicator
29
- className={cn(variant === 'primary' ? "bg-primary" : variant === 'secondary' ? "bg-secondary" : variant === 'warning' ? "bg-warning" : "bg-text", "transition-[transform_660ms_cubic-bezier(0.65,0,0.35,1)", "w-full h-full")}
32
+ className={`${variant === "primary" ? "bg-primary" : variant === "secondary" ? "bg-secondary" : variant === "warning" ? "bg-warning" : "bg-text"} transition-[transform_660ms_cubic-bezier(0.65,0,0.35,1) w-full h-full`}
30
33
  style={transformStyles}
31
34
  />
32
35
  </ProgressPrimitive.Root>
33
36
  );
34
- }
37
+ }
@@ -3,9 +3,11 @@ import {
3
3
  ChevronUpIcon,
4
4
  DropdownChevronIcon,
5
5
  } from "@noya-app/noya-icons";
6
+ import { memoGeneric } from "@noya-app/react-utils";
6
7
  import * as Select from "@radix-ui/react-select";
7
8
  import { type SelectProps } from "@radix-ui/react-select";
8
- import React, { CSSProperties, memo, useMemo } from "react";
9
+ import React, { CSSProperties, FocusEventHandler, useMemo } from "react";
10
+ import { cx } from "../utils/classNames";
9
11
  import { Button } from "./Button";
10
12
  import { renderIcon } from "./Icons";
11
13
  import { MenuItem, RegularMenuItem, styles } from "./internal/Menu";
@@ -22,6 +24,8 @@ type Props<T extends string> = {
22
24
  disabled?: boolean;
23
25
  readOnly?: boolean;
24
26
  label?: React.ReactNode;
27
+ onFocus?: FocusEventHandler;
28
+ onBlur?: FocusEventHandler;
25
29
  } & Pick<SelectProps, "open">;
26
30
 
27
31
  const readOnlyStyle: CSSProperties = {
@@ -29,7 +33,7 @@ const readOnlyStyle: CSSProperties = {
29
33
  textAlign: "left",
30
34
  };
31
35
 
32
- const labelStyles = `font-sans text-label uppercase text-text-disabled leading-[19px] font-bold text-[60%] pointer-events-none flex items-center mr-1.5 z-label`;
36
+ const labelStyles = `font-sans text-label uppercase text-text-disabled leading-[19px] font-bold text-[60%] pointer-events-none flex items-center absolute top-0 bottom-0 right-[1.3rem] z-label`;
33
37
 
34
38
  const scrollButtonStyles = `
35
39
  flex
@@ -41,7 +45,9 @@ const scrollButtonStyles = `
41
45
  hover:text-text
42
46
  `;
43
47
 
44
- export const SelectMenu = memo(function SelectMenu<T extends string = string>({
48
+ export const SelectMenu = memoGeneric(function SelectMenu<
49
+ T extends string = string,
50
+ >({
45
51
  id,
46
52
  style,
47
53
  className,
@@ -53,6 +59,8 @@ export const SelectMenu = memo(function SelectMenu<T extends string = string>({
53
59
  readOnly,
54
60
  label,
55
61
  open,
62
+ onFocus,
63
+ onBlur,
56
64
  }: Props<T>) {
57
65
  const selectedItem = menuItems.find(
58
66
  (item): item is RegularMenuItem<T> =>
@@ -66,7 +74,7 @@ export const SelectMenu = memo(function SelectMenu<T extends string = string>({
66
74
  id={id}
67
75
  style={style}
68
76
  contentStyle={readOnlyStyle}
69
- className={`${className ?? ""} flex-1 focus:z-interactable`}
77
+ className={cx(`${className} flex-1 focus:z-interactable`)}
70
78
  disabled={disabled}
71
79
  >
72
80
  {icon && (
@@ -83,11 +91,11 @@ export const SelectMenu = memo(function SelectMenu<T extends string = string>({
83
91
 
84
92
  const trigger = useMemo(
85
93
  () => (
86
- <Select.SelectTrigger asChild>
94
+ <Select.SelectTrigger asChild onFocus={onFocus} onBlur={onBlur}>
87
95
  <Button
88
96
  id={id}
89
97
  style={style}
90
- className={`${className ?? "w-full"} flex-1 focus:z-interactable`}
98
+ className={`${className ?? "w-full"} flex-1 focus:z-interactable relative`}
91
99
  disabled={disabled}
92
100
  >
93
101
  {icon && (
@@ -99,12 +107,16 @@ export const SelectMenu = memo(function SelectMenu<T extends string = string>({
99
107
  <span className="flex flex-1 mr-1.5">
100
108
  <Select.Value placeholder={placeholder} />
101
109
  </span>
102
- {label && <span className={labelStyles}>{label}</span>}
110
+ {label && (
111
+ <label htmlFor={id} className={labelStyles}>
112
+ {label}
113
+ </label>
114
+ )}
103
115
  <DropdownChevronIcon />
104
116
  </Button>
105
117
  </Select.SelectTrigger>
106
118
  ),
107
- [id, style, className, disabled, icon, placeholder, label]
119
+ [onFocus, onBlur, id, style, className, disabled, icon, placeholder, label]
108
120
  );
109
121
 
110
122
  if (readOnly) {
@@ -1,31 +1,53 @@
1
- import * as RadixSlider from '@radix-ui/react-slider';
2
- import React, { useCallback, useMemo } from 'react';
1
+ import * as RadixSlider from "@radix-ui/react-slider";
2
+ import React, { FocusEventHandler, useCallback, useMemo } from "react";
3
+ import { cx } from "../utils/classNames";
4
+
5
+ type ColorScheme = "primary" | "secondary";
3
6
 
4
7
  interface Props {
5
8
  id?: string;
9
+ style?: React.CSSProperties;
10
+ className?: string;
6
11
  value: number;
7
12
  onValueChange: (value: number) => void;
8
13
  min: number;
9
14
  max: number;
15
+ colorScheme?: ColorScheme;
16
+ label?: React.ReactNode;
17
+ onFocus?: FocusEventHandler;
18
+ onBlur?: FocusEventHandler;
10
19
  }
11
20
 
21
+ const labelStyles = `font-sans text-label uppercase text-text-disabled leading-[19px] font-bold text-[60%] pointer-events-none flex items-center z-label`;
22
+
12
23
  export const Slider = function Slider({
13
24
  id,
25
+ style,
26
+ className,
14
27
  value,
15
28
  onValueChange,
16
29
  min,
17
30
  max,
31
+ colorScheme,
32
+ label,
33
+ onFocus,
34
+ onBlur,
18
35
  }: Props) {
19
36
  const arrayValue = useMemo(
20
37
  () => [Math.min(Math.max(value, min), max)],
21
- [value, min, max],
38
+ [value, min, max]
22
39
  );
23
40
 
41
+ const percentage = useMemo(() => {
42
+ const percent = ((arrayValue[0] - min) / (max - min)) * 100;
43
+ return `${percent}%`;
44
+ }, [arrayValue, min, max]);
45
+
24
46
  const handleValueChange = useCallback(
25
47
  (arrayValue: number[]) => {
26
48
  onValueChange(arrayValue[0]);
27
49
  },
28
- [onValueChange],
50
+ [onValueChange]
29
51
  );
30
52
 
31
53
  return (
@@ -35,12 +57,52 @@ export const Slider = function Slider({
35
57
  id={id}
36
58
  value={arrayValue}
37
59
  onValueChange={handleValueChange}
38
- className="flex-1 flex relative items-center select-none touch-none h-4"
60
+ className={cx(
61
+ "flex relative items-center select-none touch-none h-[27px]",
62
+ className
63
+ )}
64
+ style={style}
65
+ onFocus={onFocus}
66
+ onBlur={onBlur}
39
67
  >
40
- <RadixSlider.Track className="bg-divider relative flex-grow h-[2px]">
41
- <RadixSlider.Range className="absolute bg-primary rounded-full h-full" />
68
+ <RadixSlider.Track className="bg-divider relative flex-grow h-full rounded overflow-hidden">
69
+ {label && (
70
+ <label
71
+ htmlFor={id}
72
+ className={cx(labelStyles, "absolute top-0 bottom-0 right-2")}
73
+ >
74
+ {label}
75
+ </label>
76
+ )}
77
+ <RadixSlider.Range
78
+ className={cx(
79
+ "absolute h-full rounded",
80
+ colorScheme === undefined && "bg-slider-border",
81
+ colorScheme === "primary" && "bg-primary",
82
+ colorScheme === "secondary" && "bg-secondary"
83
+ )}
84
+ />
42
85
  </RadixSlider.Track>
43
- <RadixSlider.Thumb className="block w-3 h-3 bg-slider-background border border-solid border-slider-border rounded-[20px] focus:outline-none" />
86
+ <RadixSlider.Thumb
87
+ className={cx(
88
+ "block w-2 h-[27px] rounded border border-solid bg-slider-background focus:outline-none cursor-ew-resize",
89
+ colorScheme === undefined && "border-slider-border",
90
+ colorScheme === "primary" && "border-primary",
91
+ colorScheme === "secondary" && "border-secondary"
92
+ )}
93
+ />
94
+ {label && (
95
+ <label
96
+ htmlFor={id}
97
+ className={cx(
98
+ labelStyles,
99
+ "absolute top-0 bottom-0 left-2 right-2 text-white overflow-hidden flex justify-end"
100
+ )}
101
+ style={{ clipPath: `inset(0 ${100 - parseFloat(percentage)}% 0 0)` }}
102
+ >
103
+ {label}
104
+ </label>
105
+ )}
44
106
  </RadixSlider.Root>
45
107
  );
46
108
  };
@@ -15,9 +15,9 @@ import {
15
15
  useSortable,
16
16
  verticalListSortingStrategy,
17
17
  } from "@dnd-kit/sortable";
18
+ import { memoGeneric } from "@noya-app/react-utils";
18
19
  import React, {
19
20
  createContext,
20
- memo,
21
21
  ReactNode,
22
22
  Ref,
23
23
  useCallback,
@@ -308,6 +308,6 @@ function SortableRoot({
308
308
  }
309
309
 
310
310
  export namespace Sortable {
311
- export const Item = memo(SortableItem);
312
- export const Root = memo(SortableRoot);
311
+ export const Item = memoGeneric(SortableItem);
312
+ export const Root = memoGeneric(SortableRoot);
313
313
  }
@@ -1,5 +1,6 @@
1
1
  import * as SwitchPrimitive from "@radix-ui/react-switch";
2
- import React from "react";
2
+ import React, { CSSProperties, FocusEventHandler } from "react";
3
+ import { cx } from "../utils/classNames";
3
4
 
4
5
  type SwitchColorScheme = "normal" | "primary" | "secondary";
5
6
 
@@ -10,6 +11,10 @@ interface Props {
10
11
  /** @default normal */
11
12
  colorScheme?: SwitchColorScheme;
12
13
  disabled?: boolean;
14
+ onFocus?: FocusEventHandler;
15
+ onBlur?: FocusEventHandler;
16
+ className?: string;
17
+ style?: CSSProperties;
13
18
  }
14
19
 
15
20
  export const Switch = function Switch({
@@ -18,16 +23,28 @@ export const Switch = function Switch({
18
23
  onChange,
19
24
  colorScheme = "normal",
20
25
  disabled,
26
+ onFocus,
27
+ onBlur,
28
+ style,
29
+ className,
21
30
  }: Props) {
22
31
  return (
23
32
  <SwitchPrimitive.Root
24
33
  id={id}
34
+ style={style}
25
35
  checked={value}
26
36
  disabled={disabled}
27
37
  onCheckedChange={(newValue) => {
28
38
  onChange(newValue);
29
39
  }}
30
- className={`all-unset w-8 h-[19px] bg-active-background rounded-full relative cursor-pointer [webkit-tap-highlight-color:rgba(0,0,0,0)] focus:ring-2 focus:ring-primary focus:ring-offset-[1px] outline-none data-[state=checked]:bg-primary ${colorScheme === "secondary" && "data-[state=checked]:bg-secondary"} focus:z-interactable`}
40
+ className={cx(
41
+ "all-unset w-8 h-[19px] bg-active-background rounded-full relative cursor-pointer [webkit-tap-highlight-color:rgba(0,0,0,0)] focus:ring-2 focus:ring-primary focus:ring-offset-[1px] outline-none data-[state=checked]:bg-primary",
42
+ colorScheme === "secondary" && "data-[state=checked]:bg-secondary",
43
+ "focus:z-interactable",
44
+ className
45
+ )}
46
+ onFocus={onFocus}
47
+ onBlur={onBlur}
31
48
  >
32
49
  <SwitchPrimitive.Thumb className="block w-[15px] h-[15px] bg-white rounded-full transition-transform translate-x-[2px] data-[state=checked]:translate-x-[15px]" />
33
50
  </SwitchPrimitive.Root>
@@ -1,6 +1,7 @@
1
1
  import React, { ForwardedRef, forwardRef, ReactHTML, ReactNode } from "react";
2
2
  import { ThemeColor } from "../theme";
3
3
  import { camelToKebabCase, KebabToCamelCase } from "../theme/themeUtils";
4
+ import { cx } from "../utils/classNames";
4
5
 
5
6
  type Variant =
6
7
  | "title"
@@ -82,7 +83,10 @@ export const Text = forwardRef(function Text(
82
83
  <Component
83
84
  // @ts-expect-error expected to be many semantic html elements
84
85
  ref={forwardedRef}
85
- className={`${textStyles[variant]} text-${camelToKebabCase(String(color)) ?? "text"} ${className ?? ""}`}
86
+ className={cx(
87
+ `${textStyles[variant]} text-${camelToKebabCase(String(color)) ?? "text"} `,
88
+ className
89
+ )}
86
90
  style={style}
87
91
  tabIndex={tabIndex}
88
92
  onClick={onClick}
@@ -1,5 +1,6 @@
1
1
  import { assignRef } from "@noya-app/react-utils";
2
2
  import React, { forwardRef, memo, useCallback, useEffect, useRef } from "react";
3
+ import { cx } from "../utils/classNames";
3
4
 
4
5
  export const useAutoResize = (value: string) => {
5
6
  const textareaRef = useRef<HTMLTextAreaElement | null>(null);
@@ -45,7 +46,10 @@ export const AutoResizingTextArea = memo(
45
46
 
46
47
  return (
47
48
  <textarea
48
- className={`font-sans text-heading5 font-normal text-text bg-input-background w-0 flex-1 py-1 px-1.5 border-none outline-none h-[100px] rounded-4 resize-none placeholder:text-text-disabled focus:ring-2 focus:ring-primary read-only:text-text-disabled focus:z-interactable ${className ?? ""}`}
49
+ className={cx(
50
+ `font-sans text-heading5 font-normal text-text bg-input-background w-0 flex-1 py-1 px-1.5 border-none outline-none h-[100px] rounded-4 resize-none placeholder:text-text-disabled focus:ring-2 focus:ring-primary read-only:text-text-disabled focus:z-interactable `,
51
+ className
52
+ )}
49
53
  ref={handleRef}
50
54
  {...rest}
51
55
  onChange={handleChange}
@@ -1,11 +1,5 @@
1
- import React, {
2
- ForwardedRef,
3
- forwardRef,
4
- memo,
5
- ReactNode,
6
- useCallback,
7
- useContext,
8
- } from "react";
1
+ import { forwardRefGeneric, memoGeneric } from "@noya-app/react-utils";
2
+ import React, { ForwardedRef, ReactNode, useCallback, useContext } from "react";
9
3
  import { IconButton } from "./IconButton";
10
4
  import { IconName, renderIcon } from "./Icons";
11
5
  import { ListView } from "./ListView";
@@ -24,7 +18,7 @@ type TreeRowBaseProps = {
24
18
  type TreeViewRowProps<MenuItemType extends string> =
25
19
  ListView.RowProps<MenuItemType> & TreeRowBaseProps;
26
20
 
27
- const TreeRow = forwardRef(function TreeRow<MenuItemType extends string>(
21
+ const TreeRow = forwardRefGeneric(function TreeRow<MenuItemType extends string>(
28
22
  {
29
23
  icon,
30
24
  expanded,
@@ -79,7 +73,7 @@ export namespace TreeView {
79
73
  export const Root = ListView.Root;
80
74
  export const RowTitle = ListView.RowTitle;
81
75
  export const EditableRowTitle = ListView.EditableRowTitle;
82
- export const Row = memo(TreeRow);
76
+ export const Row = memoGeneric(TreeRow);
83
77
  export type ClickInfo = ListView.ClickInfo;
84
78
  export type RowProps<MenuItemType extends string> =
85
79
  TreeViewRowProps<MenuItemType>;