@noya-app/noya-designsystem 0.1.24 → 0.1.26

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noya-app/noya-designsystem",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
@@ -13,7 +13,7 @@
13
13
  "@dnd-kit/core": "3.1.1",
14
14
  "@dnd-kit/modifiers": "3.0.0",
15
15
  "@dnd-kit/sortable": "4.0.0",
16
- "@noya-app/noya-colorpicker": "0.1.10",
16
+ "@noya-app/noya-colorpicker": "0.1.11",
17
17
  "@noya-app/noya-utils": "0.1.2",
18
18
  "@noya-app/noya-geometry": "0.1.4",
19
19
  "@noya-app/noya-icons": "0.1.4",
@@ -137,6 +137,12 @@ export const ButtonElement = styled.button<{
137
137
  boxShadow: "0 1px 2px rgba(0, 0, 0, 0.1)",
138
138
  fontSize: "12px",
139
139
  padding: "0px 4px",
140
+ "&:hover": {
141
+ opacity: 0.8,
142
+ },
143
+ "&:active": {
144
+ opacity: 0.9,
145
+ },
140
146
  }),
141
147
  display: "flex",
142
148
  alignItems: "center",
@@ -186,6 +192,7 @@ export interface ButtonRootProps {
186
192
  contentStyle?: CSSProperties;
187
193
  defaultBackground?: string;
188
194
  as?: React.ElementType;
195
+ htmlFor?: string;
189
196
  href?: string;
190
197
  target?: string;
191
198
  rel?: string;
@@ -5,7 +5,7 @@ import { useDesignSystemTheme } from "../contexts/DesignSystemConfiguration";
5
5
  import { useHover } from "../hooks/useHover";
6
6
 
7
7
  type ChipColorScheme = "primary" | "secondary" | "error";
8
- type ChipSize = "small" | "medium";
8
+ type ChipSize = "small" | "medium" | "large";
9
9
  type ChipVariant = "solid" | "outlined" | "ghost";
10
10
 
11
11
  const ChipElement = styled.span<{
@@ -24,9 +24,9 @@ const ChipElement = styled.span<{
24
24
 
25
25
  const backgroundColor =
26
26
  $colorScheme === "primary"
27
- ? "rgb(238, 229, 255)"
27
+ ? theme.colors.primaryPastel
28
28
  : $colorScheme === "secondary"
29
- ? "rgb(205, 238, 231)"
29
+ ? theme.colors.secondaryPastel
30
30
  : $colorScheme === "error"
31
31
  ? "rgb(255, 219, 219)"
32
32
  : theme.colors.inputBackground;
@@ -49,15 +49,20 @@ const ChipElement = styled.span<{
49
49
  // lineHeight: "1.4",
50
50
  lineHeight: "15px",
51
51
  whiteSpace: "pre",
52
- ...($size === "medium"
52
+ ...($size === "large"
53
53
  ? {
54
54
  fontSize: "11px",
55
55
  padding: "4px 8px",
56
56
  }
57
- : {
58
- fontSize: "9px",
59
- padding: "0px 4px",
60
- }),
57
+ : $size === "medium"
58
+ ? {
59
+ fontSize: "10px",
60
+ padding: "2px 6px",
61
+ }
62
+ : {
63
+ fontSize: "9px",
64
+ padding: "0px 4px",
65
+ }),
61
66
  ...($monospace && {
62
67
  fontFamily: theme.fonts.monospace,
63
68
  }),
@@ -96,7 +101,7 @@ const DeleteElement = styled(Cross1Icon)<{
96
101
  cursor: "pointer",
97
102
  opacity: 0.5,
98
103
 
99
- ...(size === "medium"
104
+ ...(size === "large"
100
105
  ? {
101
106
  marginLeft: "2px",
102
107
  // top: '-1px',
@@ -124,7 +129,7 @@ const AddElement = styled(PlusIcon).withConfig({
124
129
  cursor: "pointer",
125
130
  opacity: 0.5,
126
131
 
127
- ...(size === "medium"
132
+ ...(size === "large"
128
133
  ? {
129
134
  marginRight: "2px",
130
135
  top: "-1px",
@@ -168,7 +173,7 @@ export const Chip = memo(
168
173
  addable,
169
174
  clickable,
170
175
  style,
171
- size = "medium",
176
+ size = "large",
172
177
  variant = "solid",
173
178
  monospace = false,
174
179
  tabIndex,
@@ -370,40 +370,46 @@ const InputFieldInput = forwardRef(function InputFieldInput(
370
370
  );
371
371
  });
372
372
 
373
- const InputFieldTypeahead = (props: { value: string }) => {
373
+ const InputFieldTypeahead = (props: { prefix: string; value: string }) => {
374
374
  const {
375
375
  labelPosition,
376
376
  labelSize,
377
377
  hasDropdown,
378
378
  hasLabel,
379
379
  size,
380
- onFocusChange,
380
+ // onFocusChange,
381
381
  } = useContext(InputFieldContext);
382
382
 
383
383
  return (
384
384
  <InputElement
385
+ as="span"
385
386
  $labelPosition={labelPosition}
386
387
  $labelSize={labelSize}
387
388
  $hasLabel={hasLabel}
388
389
  $hasDropdown={hasDropdown}
389
390
  $size={size}
390
- onFocusChange={onFocusChange}
391
- readOnly
392
- placeholder={props.value}
391
+ // onFocusChange={onFocusChange}
392
+ // readOnly
393
+ // placeholder={props.value}
393
394
  value=""
394
395
  style={{
395
396
  position: "absolute",
396
- top: -1,
397
+ top: 0,
397
398
  left: 0,
398
399
  right: 0,
399
- bottom: -1,
400
+ bottom: 0,
400
401
  width: "100%",
401
402
  height: "100%",
402
403
  pointerEvents: "none",
403
404
  background: "transparent",
404
405
  boxShadow: "none",
405
406
  }}
406
- />
407
+ >
408
+ <span style={{ opacity: 0 }}>{props.prefix}</span>
409
+ <span style={{ opacity: 0.5 }}>
410
+ {props.value.slice(props.prefix.length)}
411
+ </span>
412
+ </InputElement>
407
413
  );
408
414
  };
409
415
 
@@ -662,6 +668,39 @@ function InputFieldRoot({
662
668
  );
663
669
  }
664
670
 
671
+ const PrimitiveInputField = styled.input(({ theme, readOnly, disabled }) => {
672
+ return {
673
+ // placeholder
674
+ "&::placeholder": {
675
+ color: theme.colors.textDisabled,
676
+ },
677
+ ...theme.textStyles.small,
678
+ color: readOnly
679
+ ? theme.colors.textMuted
680
+ : disabled
681
+ ? theme.colors.textDisabled
682
+ : theme.colors.text,
683
+ width: "0px", // Reset intrinsic width
684
+ flex: "1 1 0px",
685
+ position: "relative",
686
+ border: "0",
687
+ outline: "none",
688
+ minWidth: "0",
689
+ alignSelf: "stretch",
690
+ borderRadius: "4px",
691
+ paddingTop: "4px",
692
+ paddingBottom: "4px",
693
+ paddingLeft: "6px",
694
+ paddingRight: "6px",
695
+ background: theme.colors.inputBackground,
696
+ "&:focus": {
697
+ boxShadow: `0 0 0 2px ${theme.colors.primary}`,
698
+ },
699
+ userSelect: "all",
700
+ pointerEvents: "all",
701
+ };
702
+ });
703
+
665
704
  export namespace InputField {
666
705
  export const Root = memo(InputFieldRoot);
667
706
  export const Input = memo(InputFieldInput);
@@ -670,4 +709,5 @@ export namespace InputField {
670
709
  export const DropdownMenu = InputFieldDropdownMenu;
671
710
  export const Button = InputFieldButton;
672
711
  export const Label = InputFieldLabel;
712
+ export const PrimitiveElement = PrimitiveInputField;
673
713
  }
@@ -468,9 +468,11 @@ export const InputFieldWithCompletions = memo(
468
468
  aria-expanded={isFocused && display !== "none"}
469
469
  aria-controls="component-listbox"
470
470
  />
471
- {filter && typeaheadValue && typeaheadValue.startsWith(filter) && (
472
- <InputField.Typeahead value={typeaheadValue} />
473
- )}
471
+ {filter &&
472
+ typeaheadValue &&
473
+ typeaheadValue.toLowerCase().startsWith(filter.toLowerCase()) && (
474
+ <InputField.Typeahead value={typeaheadValue} prefix={filter} />
475
+ )}
474
476
  {(!isFocused || !hideChildrenWhenFocused) && children}
475
477
  {loading && isFocused && (
476
478
  <InputField.Label>
@@ -31,7 +31,7 @@ export const Title = styled.div<{
31
31
  }
32
32
  : {
33
33
  ...theme.textStyles.label,
34
- color: theme.colors.textSubtle,
34
+ color: theme.colors.textMuted,
35
35
  fontWeight: "bold",
36
36
  }),
37
37
  }));
@@ -66,14 +66,15 @@ type ListRowContextValue = {
66
66
  // The dragged item isn't passed a context value (doing so causes an infinite loop),
67
67
  // so we pass this separately. Rows need the indentation to look correct when dragging,
68
68
  // and the dragged row doesn't have a ListRowContextValue.
69
- type DraggingContextValue = {
69
+ type ListViewDraggingContextValue = {
70
70
  indentation: number;
71
71
  isDragging?: boolean;
72
72
  };
73
73
 
74
- const DraggingContext = createContext<DraggingContextValue>({
74
+ const ListViewDraggingContext = createContext<ListViewDraggingContextValue>({
75
75
  indentation: 12,
76
76
  });
77
+ (ListViewDraggingContext as any).displayName = "ListViewDraggingContext";
77
78
 
78
79
  const ListRowContext = createContext<ListRowContextValue>({
79
80
  marginType: "none",
@@ -88,6 +89,7 @@ const ListRowContext = createContext<ListRowContextValue>({
88
89
  isSectionHeader: false,
89
90
  colorScheme: "primary",
90
91
  });
92
+ (ListRowContext as any).displayName = "ListRowContext";
91
93
 
92
94
  /* ----------------------------------------------------------------------------
93
95
  * RowTitle
@@ -333,6 +335,7 @@ interface ListViewRowProps<MenuItemType extends string = string> {
333
335
  onMenuOpenChange?: (isOpen: boolean) => void;
334
336
  onKeyDown?: (event: React.KeyboardEvent) => void;
335
337
  style?: CSSProperties;
338
+ className?: string;
336
339
  dragIndicatorStyle?:
337
340
  | CSSProperties
338
341
  | ((props: {
@@ -367,6 +370,7 @@ const ListViewRow = forwardRef(function ListViewRow<
367
370
  onKeyDown,
368
371
  style,
369
372
  dragIndicatorStyle,
373
+ className,
370
374
  }: ListViewRowProps<MenuItemType>,
371
375
  forwardedRef: ForwardedRef<HTMLElement>
372
376
  ) {
@@ -386,7 +390,7 @@ const ListViewRow = forwardRef(function ListViewRow<
386
390
  const { hoverProps } = useHover({
387
391
  onHoverChange,
388
392
  });
389
- const { indentation, isDragging } = useContext(DraggingContext);
393
+ const { indentation, isDragging } = useContext(ListViewDraggingContext);
390
394
 
391
395
  const handlePress = useCallback(
392
396
  (event: React.MouseEvent) => {
@@ -463,6 +467,7 @@ const ListViewRow = forwardRef(function ListViewRow<
463
467
  { [pressEventName]: handlePress }
464
468
  )}
465
469
  tabIndex={tabIndex}
470
+ className={className}
466
471
  >
467
472
  {relativeDropPosition && (
468
473
  <ListViewDragIndicatorElement
@@ -524,6 +529,7 @@ const ListViewRow = forwardRef(function ListViewRow<
524
529
  const RenderItemContext = createContext<(index: number) => ReactNode>(
525
530
  () => null
526
531
  );
532
+ (RenderItemContext as any).displayName = "RenderItemContext";
527
533
 
528
534
  const VirtualizedListRow = memo(function VirtualizedListRow({
529
535
  index,
@@ -761,8 +767,29 @@ const ListViewRootInner = forwardRef(function ListViewRootInner<T>(
761
767
  [data, renderItem]
762
768
  );
763
769
 
770
+ // TODO: Do we still use section header properties anywhere?
771
+ const defaultContextValue = useMemo(
772
+ (): ListRowContextValue => ({
773
+ colorScheme,
774
+ marginType: "none",
775
+ selectedPosition: "only",
776
+ sortable,
777
+ expandable,
778
+ divider,
779
+ pressEventName,
780
+ variant,
781
+ gap,
782
+ sectionHeaderVariant: "normal",
783
+ isSectionHeader: false,
784
+ }),
785
+ [colorScheme, sortable, expandable, divider, pressEventName, variant, gap]
786
+ );
787
+
764
788
  const getItemContextValue = useCallback(
765
789
  (i: number): ListRowContextValue | undefined => {
790
+ if (variant === "bare" || variant === "normal")
791
+ return defaultContextValue;
792
+
766
793
  const current = renderChild(i);
767
794
 
768
795
  if (!isValidElement(current)) return;
@@ -827,6 +854,8 @@ const ListViewRootInner = forwardRef(function ListViewRootInner<T>(
827
854
  };
828
855
  },
829
856
  [
857
+ variant,
858
+ defaultContextValue,
830
859
  renderChild,
831
860
  data.length,
832
861
  colorScheme,
@@ -834,7 +863,6 @@ const ListViewRootInner = forwardRef(function ListViewRootInner<T>(
834
863
  expandable,
835
864
  divider,
836
865
  pressEventName,
837
- variant,
838
866
  sectionHeaderVariant,
839
867
  gap,
840
868
  ]
@@ -848,9 +876,9 @@ const ListViewRootInner = forwardRef(function ListViewRootInner<T>(
848
876
  const renderOverlay = useCallback(
849
877
  (index: number) => (
850
878
  <div style={{ opacity: 0.25 }}>
851
- <DraggingContext.Provider value={draggingContextOverlayValue}>
879
+ <ListViewDraggingContext.Provider value={draggingContextOverlayValue}>
852
880
  {renderItem(data[index], index, { isDragging: true })}
853
- </DraggingContext.Provider>
881
+ </ListViewDraggingContext.Provider>
854
882
  </div>
855
883
  ),
856
884
  [draggingContextOverlayValue, renderItem, data]
@@ -916,7 +944,7 @@ const ListViewRootInner = forwardRef(function ListViewRootInner<T>(
916
944
  const draggingContextValue = useMemo(() => ({ indentation }), [indentation]);
917
945
 
918
946
  return (
919
- <DraggingContext.Provider value={draggingContextValue}>
947
+ <ListViewDraggingContext.Provider value={draggingContextValue}>
920
948
  <RootContainer
921
949
  id={id}
922
950
  className={className}
@@ -945,7 +973,7 @@ const ListViewRootInner = forwardRef(function ListViewRootInner<T>(
945
973
  )
946
974
  )}
947
975
  </RootContainer>
948
- </DraggingContext.Provider>
976
+ </ListViewDraggingContext.Provider>
949
977
  );
950
978
  });
951
979
 
@@ -1,6 +1,6 @@
1
1
  import { DropdownChevronIcon } from "@noya-app/noya-icons";
2
2
  import * as Select from "@radix-ui/react-select";
3
- import React, { memo } from "react";
3
+ import React, { CSSProperties, memo } from "react";
4
4
  import { styled } from "styled-components";
5
5
  import { Button } from "./Button";
6
6
  import { renderIcon } from "./Icons";
@@ -15,6 +15,17 @@ type Props<T extends string> = {
15
15
  value: T;
16
16
  onSelect?: (value: T) => void;
17
17
  placeholder?: string;
18
+ disabled?: boolean;
19
+ readOnly?: boolean;
20
+ };
21
+
22
+ const readOnlyStyle: CSSProperties = {
23
+ justifyContent: "flex-start",
24
+ textAlign: "left",
25
+ };
26
+
27
+ const flexStyle: CSSProperties = {
28
+ flex: 1,
18
29
  };
19
30
 
20
31
  export const SelectMenu = memo(function SelectMenu<T extends string = string>({
@@ -25,6 +36,8 @@ export const SelectMenu = memo(function SelectMenu<T extends string = string>({
25
36
  value,
26
37
  onSelect,
27
38
  placeholder,
39
+ disabled,
40
+ readOnly,
28
41
  }: Props<T>) {
29
42
  const selectedItem = menuItems.find(
30
43
  (item): item is RegularMenuItem<T> =>
@@ -32,18 +45,40 @@ export const SelectMenu = memo(function SelectMenu<T extends string = string>({
32
45
  );
33
46
  const icon = selectedItem?.icon;
34
47
 
48
+ if (readOnly) {
49
+ return (
50
+ <Button
51
+ id={id}
52
+ style={style}
53
+ contentStyle={readOnlyStyle}
54
+ className={className}
55
+ disabled={disabled}
56
+ >
57
+ {icon && (
58
+ <>
59
+ {renderIcon(icon)}
60
+ <Spacer.Horizontal inline size={6} />
61
+ </>
62
+ )}
63
+ <span style={flexStyle}>{selectedItem?.title ?? value}</span>
64
+ </Button>
65
+ );
66
+ }
67
+
35
68
  return (
36
69
  <Select.Root value={value} onValueChange={onSelect}>
37
70
  <Select.SelectTrigger asChild>
38
- <Button id={id} style={style} className={className}>
71
+ <Button id={id} style={style} className={className} disabled={disabled}>
39
72
  {icon && (
40
73
  <>
41
74
  {renderIcon(icon)}
42
- <Spacer.Horizontal size={8} />
75
+ <Spacer.Horizontal inline size={6} />
43
76
  </>
44
77
  )}
45
- <Select.Value placeholder={placeholder} />
46
- <Spacer.Horizontal />
78
+ <span style={flexStyle}>
79
+ <Select.Value placeholder={placeholder} />
80
+ </span>
81
+ <Spacer.Horizontal inline size={6} />
47
82
  <DropdownChevronIcon />
48
83
  </Button>
49
84
  </Select.SelectTrigger>
@@ -45,17 +45,20 @@ interface Props {
45
45
  value: boolean;
46
46
  onChange: (value: boolean) => void;
47
47
  colorScheme?: SwitchColorScheme;
48
+ disabled?: boolean;
48
49
  }
49
50
 
50
51
  export const Switch = function Switch({
51
52
  value,
52
53
  onChange,
53
54
  colorScheme = "normal",
55
+ disabled,
54
56
  }: Props) {
55
57
  return (
56
58
  <SwitchRoot
57
59
  $colorScheme={colorScheme}
58
60
  checked={value}
61
+ disabled={disabled}
59
62
  onCheckedChange={(newValue) => {
60
63
  onChange(newValue);
61
64
  }}
@@ -55,6 +55,7 @@ interface Props extends StyleProps {
55
55
  as?: keyof ReactHTML;
56
56
  href?: string;
57
57
  className?: string;
58
+ style?: React.CSSProperties;
58
59
  variant: keyof Theme["textStyles"];
59
60
  breakpoints?: BreakpointCollection<StyleProps> | null | false;
60
61
  color?: ThemeColorName;
@@ -89,6 +90,7 @@ export const Text = forwardRef(function Text(
89
90
  tabIndex,
90
91
  onClick,
91
92
  onKeyDown,
93
+ style,
92
94
  ...rest
93
95
  }: Props,
94
96
  forwardedRef: ForwardedRef<HTMLElement>
@@ -100,6 +102,7 @@ export const Text = forwardRef(function Text(
100
102
  ref={forwardedRef}
101
103
  as={element}
102
104
  className={className}
105
+ style={style}
103
106
  tabIndex={tabIndex}
104
107
  $variant={variant}
105
108
  $breakpoints={breakpoints}
@@ -0,0 +1,82 @@
1
+ import { assignRef } from "@noya-app/react-utils";
2
+ import React, { forwardRef, memo, useCallback, useEffect, useRef } from "react";
3
+ import styled from "styled-components";
4
+
5
+ const TextAreaElement = styled.textarea(({ theme }) => ({
6
+ ...theme.textStyles.small,
7
+ color: theme.colors.text,
8
+ background: theme.colors.inputBackground,
9
+ width: "0px",
10
+ flex: "1 1 auto",
11
+ padding: "4px 6px",
12
+ border: "none",
13
+ outline: "none",
14
+ height: 100,
15
+ borderRadius: "4px",
16
+ "&::placeholder": {
17
+ color: theme.colors.textDisabled,
18
+ },
19
+ "&:focus": {
20
+ boxShadow: `0 0 0 2px ${theme.colors.primary}`,
21
+ },
22
+ // readonly
23
+ "&:read-only": {
24
+ color: theme.colors.textDisabled,
25
+ },
26
+ resize: "none",
27
+ }));
28
+
29
+ export const useAutoResize = (value: string) => {
30
+ const textareaRef = useRef<HTMLTextAreaElement | null>(null);
31
+
32
+ useEffect(() => {
33
+ if (!textareaRef.current) return;
34
+
35
+ textareaRef.current.style.height = "auto"; // Reset the height
36
+ textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
37
+ }, [value]);
38
+
39
+ return textareaRef;
40
+ };
41
+
42
+ export const AutoResizingTextArea = memo(
43
+ forwardRef(function AutoResizingTextArea(
44
+ {
45
+ value,
46
+ onChangeText,
47
+ ...rest
48
+ }: Omit<
49
+ React.ComponentProps<typeof TextAreaElement>,
50
+ "onChange" | "value"
51
+ > & {
52
+ onChangeText: (value: string) => void;
53
+ value?: string;
54
+ },
55
+ forwardedRef: React.ForwardedRef<HTMLTextAreaElement>
56
+ ) {
57
+ const ref = useAutoResize(value || rest.placeholder || "");
58
+
59
+ const handleChange = useCallback(
60
+ (event: React.ChangeEvent<HTMLTextAreaElement>) =>
61
+ onChangeText(event.target.value),
62
+ [onChangeText]
63
+ );
64
+
65
+ const handleRef = useCallback(
66
+ (value: HTMLTextAreaElement | null) => {
67
+ ref.current = value;
68
+ assignRef(forwardedRef, value);
69
+ },
70
+ [ref, forwardedRef]
71
+ );
72
+
73
+ return (
74
+ <TextAreaElement
75
+ ref={handleRef}
76
+ {...rest}
77
+ onChange={handleChange}
78
+ value={value}
79
+ />
80
+ );
81
+ })
82
+ );
package/src/index.tsx CHANGED
@@ -49,6 +49,7 @@ export * from "./components/Spacer";
49
49
  export * from "./components/Stack";
50
50
  export * from "./components/Switch";
51
51
  export * from "./components/Text";
52
+ export * from "./components/TextArea";
52
53
  export * from "./components/Toast";
53
54
  export * from "./components/Tooltip";
54
55
  export * from "./components/TreeView";