@iota/apps-ui-kit 0.7.0 → 0.7.2

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 (53) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/index.d.ts +51 -25
  3. package/dist/index.es.js +2213 -2151
  4. package/dist/index.es.js.map +1 -1
  5. package/dist/index.umd.js +10 -10
  6. package/dist/index.umd.js.map +1 -1
  7. package/dist/style.css +1 -1
  8. package/package.json +3 -2
  9. package/src/lib/components/atoms/button/Button.tsx +1 -0
  10. package/src/lib/components/atoms/button-segment/ButtonSegment.tsx +1 -1
  11. package/src/lib/components/atoms/checkbox/Checkbox.tsx +0 -1
  12. package/src/lib/components/atoms/index.ts +1 -0
  13. package/src/lib/components/atoms/list-item/ListItem.tsx +1 -1
  14. package/src/lib/components/atoms/radio-button/RadioButton.tsx +1 -1
  15. package/src/lib/components/atoms/skeleton/Skeleton.tsx +5 -20
  16. package/src/lib/components/index.ts +0 -2
  17. package/src/lib/components/molecules/chip/Chip.tsx +51 -17
  18. package/src/lib/components/molecules/chip/chip.classes.ts +36 -14
  19. package/src/lib/components/molecules/chip/chip.enums.ts +6 -3
  20. package/src/lib/components/molecules/input/Input.tsx +34 -17
  21. package/src/lib/components/molecules/input/TextArea.tsx +5 -4
  22. package/src/lib/components/molecules/navbar-item/NavbarItem.tsx +0 -1
  23. package/src/lib/components/molecules/navbar-item/NavbarItemHorizontal.tsx +1 -1
  24. package/src/lib/components/molecules/navbar-item/NavbarItemVertical.tsx +1 -1
  25. package/src/lib/components/molecules/select/Select.tsx +24 -4
  26. package/src/lib/components/organisms/dialog/Dialog.tsx +37 -22
  27. package/src/lib/constants/colors.constants.ts +12 -5
  28. package/src/lib/enums/index.ts +1 -0
  29. package/src/lib/enums/theme.enums.ts +8 -0
  30. package/src/lib/index.ts +2 -0
  31. package/src/lib/styles/base.css +40 -0
  32. package/src/lib/styles/components.css +52 -4
  33. package/src/lib/styles/index.css +4 -753
  34. package/src/lib/styles/utilities.css +11 -3
  35. package/src/lib/styles/variables/dark-variables.css +262 -0
  36. package/src/lib/styles/variables/general-variables.css +9 -0
  37. package/src/lib/styles/variables/light-variables.css +276 -0
  38. package/src/lib/styles/variables/names-variables.css +270 -0
  39. package/src/lib/tailwind/base.presets.ts +4 -0
  40. package/src/lib/tailwind/constants/gradients.constants.ts +6 -0
  41. package/src/storybook/blocks/TypeSet.tsx +3 -3
  42. package/src/storybook/constants/gradients.constants.ts +8 -0
  43. package/src/storybook/helpers/formatGradientNames.ts +15 -0
  44. package/src/storybook/stories/atoms/Skeleton.stories.tsx +5 -5
  45. package/src/storybook/stories/components/DocsSyncTheme.tsx +20 -0
  46. package/src/storybook/stories/components/index.ts +1 -0
  47. package/src/storybook/stories/design-tokens/colors.stories.mdx +12 -0
  48. package/src/storybook/stories/design-tokens/icons.stories.mdx +2 -0
  49. package/src/storybook/stories/design-tokens/typeset.stories.mdx +2 -0
  50. package/src/storybook/stories/design-tokens/variable-spacing.stories.mdx +2 -1
  51. package/src/storybook/stories/molecules/Chip.stories.tsx +5 -0
  52. package/src/storybook/stories/molecules/Input.stories.tsx +79 -2
  53. package/src/storybook/stories/organisms/Dialog.stories.tsx +54 -28
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@iota/apps-ui-kit",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
+ "sideEffects": false,
4
5
  "repository": {
5
6
  "type": "git",
6
7
  "url": "github.com:iotaledger/iota.git"
@@ -29,7 +30,7 @@
29
30
  "classnames": "^2.5.1",
30
31
  "lodash.merge": "^4.6.2",
31
32
  "react-number-format": "^5.2.2",
32
- "@iota/apps-ui-icons": "0.4.0"
33
+ "@iota/apps-ui-icons": "0.4.1"
33
34
  },
34
35
  "peerDependencies": {
35
36
  "react": "^18 || ^19",
@@ -87,6 +87,7 @@ export function Button({
87
87
  backgroundColors,
88
88
  fullWidth && 'w-full',
89
89
  !iconAfterText ? 'flex-row' : 'flex-row-reverse',
90
+ `btn btn-${type}`,
90
91
  )}
91
92
  disabled={disabled}
92
93
  tabIndex={tabIndex}
@@ -77,7 +77,7 @@ export function ButtonSegment({
77
77
  <ButtonUnstyled
78
78
  onClick={onClick}
79
79
  className={cx(
80
- 'enabled:state-layer relative flex items-center disabled:opacity-40',
80
+ 'enabled:state-layer-secondary relative flex items-center disabled:opacity-40',
81
81
  backgroundColors,
82
82
  textColors,
83
83
  padding,
@@ -101,7 +101,6 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
101
101
  'peer-[&:is(:checked,:indeterminate)]:checkbox-border-checked',
102
102
  'peer-[&:is(:checked,:indeterminate)]:checkbox-bg-checked',
103
103
  'peer-[&:is(:checked,:indeterminate)]:checkbox-text-checked',
104
- 'peer-disabled:peer-[&:is(:checked,:indeterminate)]:[&_svg]:hidden',
105
104
  'peer-disabled:peer-[&:not(:checked,:indeterminate)]:checkbox-border-color-disabled',
106
105
  'peer-disabled:peer-[&:is(:checked,:indeterminate)]:checkbox-border-color-disabled-checked',
107
106
  'peer-disabled:peer-[&:is(:checked,:indeterminate)]:checkbox-bg-color-disabled-checked',
@@ -23,3 +23,4 @@ export * from './placeholder';
23
23
  export * from './skeleton';
24
24
  export * from './video';
25
25
  export * from './toggle';
26
+ export * from './secondary-text';
@@ -69,7 +69,7 @@ export function ListItem({
69
69
  !isDisabled && onClick ? 'cursor-pointer' : 'cursor-default',
70
70
  {
71
71
  'list-item-highlight-bg': isHighlighted,
72
- 'state-layer': !isDisabled,
72
+ 'state-layer-secondary': !isDisabled,
73
73
  },
74
74
  )}
75
75
  >
@@ -40,7 +40,7 @@ function RadioButton({
40
40
  >
41
41
  <div
42
42
  className={cx('relative flex h-10 w-10 items-center justify-center rounded-full', {
43
- 'state-layer': !isDisabled,
43
+ 'state-layer-secondary': !isDisabled,
44
44
  })}
45
45
  >
46
46
  <input
@@ -2,41 +2,26 @@
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
4
  import cx from 'classnames';
5
+ import React from 'react';
5
6
 
6
- interface SkeletonLoaderProps {
7
- /**
8
- * Width class for the skeleton div.
9
- */
10
- widthClass?: string;
11
- /**
12
- * Height class for the skeleton div.
13
- */
14
- heightClass?: string;
7
+ interface SkeletonLoaderProps extends Pick<React.HTMLAttributes<HTMLDivElement>, 'className'> {
15
8
  /**
16
9
  * If true, the skeleton will use darker neutral colors.
17
10
  */
18
11
  hasSecondaryColors?: boolean;
19
- /**
20
- * Whether the class `rounded-full` should be applied. Defaults to true.
21
- */
22
- isRounded?: boolean;
23
12
  }
24
13
 
25
14
  export function Skeleton({
26
15
  children,
27
- widthClass = 'w-full',
28
- heightClass = 'h-3',
16
+ className,
29
17
  hasSecondaryColors,
30
- isRounded = true,
31
18
  }: React.PropsWithChildren<SkeletonLoaderProps>): React.JSX.Element {
32
19
  return (
33
20
  <div
34
21
  className={cx(
35
- 'animate-pulse rounded-full',
36
- widthClass,
37
- heightClass,
38
- isRounded && 'rounded-full',
22
+ 'h-3 w-full animate-pulse rounded-full',
39
23
  hasSecondaryColors ? 'skeleton-secondary-bg' : 'skeleton-bg',
24
+ className,
40
25
  )}
41
26
  >
42
27
  {children}
@@ -1,8 +1,6 @@
1
1
  // Copyright (c) 2024 IOTA Stiftung
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
- import '../styles/index.css';
5
-
6
4
  export * from './atoms';
7
5
  export * from './molecules';
8
6
  export * from './organisms';
@@ -1,18 +1,22 @@
1
1
  // Copyright (c) 2024 IOTA Stiftung
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
+ import { ButtonUnstyled } from '@/components/atoms/button';
5
+ import { Close } from '@iota/apps-ui-icons';
4
6
  import cx from 'classnames';
5
- import { ChipSize, ChipState } from './chip.enums';
6
7
  import {
7
- BORDER_CLASSES,
8
8
  BACKGROUND_CLASSES,
9
+ BG_SELECTED_OUTLINE,
10
+ BG_SELECTED_OVERLAY,
11
+ BORDER_CLASSES,
12
+ CLOSE_ICON_INTERACTIVE,
13
+ FOCUS_CLASSES,
9
14
  ROUNDED_CLASS,
10
15
  STATE_LAYER_CLASSES,
11
16
  TEXT_COLOR,
12
- FOCUS_CLASSES,
17
+ TEXT_COLOR_SELECTED_OUTLINE,
13
18
  } from './chip.classes';
14
- import { ButtonUnstyled } from '@/components/atoms/button';
15
- import { Close } from '@iota/apps-ui-icons';
19
+ import { ChipSize, ChipType } from './chip.enums';
16
20
 
17
21
  interface ChipProps {
18
22
  /**
@@ -51,6 +55,10 @@ interface ChipProps {
51
55
  * The button is disabled or not.
52
56
  */
53
57
  disabled?: boolean;
58
+ /**
59
+ * The type of the Chip
60
+ */
61
+ type?: ChipType;
54
62
  /**
55
63
  * The size of the chip.
56
64
  */
@@ -59,8 +67,9 @@ interface ChipProps {
59
67
 
60
68
  export function Chip({
61
69
  label,
62
- showClose,
70
+ type = ChipType.Outline,
63
71
  selected,
72
+ showClose,
64
73
  onClose,
65
74
  onClick,
66
75
  avatar,
@@ -69,16 +78,30 @@ export function Chip({
69
78
  disabled,
70
79
  size = ChipSize.Default,
71
80
  }: ChipProps) {
72
- const chipState = selected ? ChipState.Selected : ChipState.Default;
81
+ const isOutlineSelected = type === ChipType.Outline && selected;
82
+ const outlineStyle = selected
83
+ ? type === ChipType.Outline
84
+ ? 'outline-transparent names:outline-names-neutral-20'
85
+ : 'chip-outline-color'
86
+ : 'outline-transparent';
87
+
88
+ const focusOutlineStyle = cx(
89
+ 'group-focus:chip-outline-color-active',
90
+ type === ChipType.Outline && 'names:group-focus:outline-transparent',
91
+ );
92
+ const selectedOverlayBg =
93
+ selected && !disabled && type !== ChipType.Outline ? BG_SELECTED_OVERLAY : '';
94
+
73
95
  return (
74
96
  <ButtonUnstyled
75
97
  onClick={onClick}
76
98
  className={cx(
77
- 'border transition-all duration-500 ease-in-out disabled:opacity-40',
99
+ 'group border disabled:opacity-40',
78
100
  ROUNDED_CLASS,
79
- BACKGROUND_CLASSES[chipState],
80
- BORDER_CLASSES[chipState],
81
- FOCUS_CLASSES,
101
+ isOutlineSelected
102
+ ? BG_SELECTED_OUTLINE[ChipType.Outline]
103
+ : BACKGROUND_CLASSES[type],
104
+ selected ? 'border-transparent' : BORDER_CLASSES[type],
82
105
  )}
83
106
  disabled={disabled}
84
107
  >
@@ -87,10 +110,16 @@ export function Chip({
87
110
  'flex h-full w-full flex-row items-center gap-x-2',
88
111
  avatar ? 'py-xxs' : 'py-[6px]',
89
112
  avatar ? 'pl-xxs' : leadingElement ? 'pl-xs' : 'pl-sm',
113
+ showClose ? 'pr-xs' : 'pr-sm',
90
114
  ROUNDED_CLASS,
115
+ isOutlineSelected
116
+ ? TEXT_COLOR_SELECTED_OUTLINE[ChipType.Outline]
117
+ : TEXT_COLOR[type],
118
+ outlineStyle,
119
+ !disabled && focusOutlineStyle,
91
120
  !disabled && STATE_LAYER_CLASSES,
92
- showClose ? 'pr-xs' : 'pr-sm',
93
- TEXT_COLOR[chipState],
121
+ FOCUS_CLASSES,
122
+ selectedOverlayBg,
94
123
  )}
95
124
  >
96
125
  {avatar ?? leadingElement}
@@ -99,12 +128,17 @@ export function Chip({
99
128
  </span>
100
129
  {trailingElement}
101
130
  {showClose && (
102
- <ButtonUnstyled
131
+ <div
103
132
  onClick={onClose}
104
- className="cursor-pointer [&_svg]:h-4 [&_svg]:w-4"
133
+ className={cx(disabled ? 'cursor-default' : 'cursor-pointer')}
105
134
  >
106
- <Close />
107
- </ButtonUnstyled>
135
+ <Close
136
+ className={cx(
137
+ 'h-4 w-4 transition-opacity duration-150',
138
+ disabled ? '' : selected ? 'opacity-100' : CLOSE_ICON_INTERACTIVE,
139
+ )}
140
+ />
141
+ </div>
108
142
  )}
109
143
  </span>
110
144
  </ButtonUnstyled>
@@ -1,32 +1,54 @@
1
1
  // Copyright (c) 2024 IOTA Stiftung
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
- import { ChipState } from './chip.enums';
4
+ import { ChipType } from './chip.enums';
5
5
 
6
6
  export const ROUNDED_CLASS = 'rounded-full';
7
7
 
8
- export const BACKGROUND_CLASSES: Record<ChipState, string> = {
9
- [ChipState.Default]: 'bg-transparent',
10
- [ChipState.Selected]: 'chip-bg-selected',
8
+ export const BACKGROUND_CLASSES: Record<ChipType, string> = {
9
+ [ChipType.Outline]: 'bg-transparent',
10
+ [ChipType.Elevated]: 'chip-bg-elevated',
11
+ [ChipType.Success]: 'bg-success-surface',
12
+ [ChipType.Brand]: 'chip-bg-brand',
13
+ [ChipType.Error]: 'bg-error-surface',
11
14
  };
12
15
 
13
- const STATE_LAYER_OUTLINE =
14
- 'outline outline-1 outline-transparent hover:outline-shader-primary-light-8 active:outline-shader-primary-light-12 dark:hover:outline-shader-primary-dark-8 dark:active:outline-shader-primary-dark-12';
16
+ export const STATE_LAYER_OUTLINE =
17
+ 'outline outline-1 hover:chip-outline-color-hover active:chip-outline-color-active';
15
18
 
16
- const STATE_LAYER_BG_CLASSES =
17
- 'hover:bg-shader-primary-light-8 active:bg-shader-primary-light-12 dark:hover:bg-shader-primary-dark-8 dark:active:bg-shader-primary-dark-12 focus:bg-shader-primary-light-12 dark:focus:bg-shader-primary-dark-12';
19
+ export const STATE_LAYER_BG_CLASSES =
20
+ 'hover:chip-bg-color-hover active:chip-bg-color-active group-focus:chip-bg-color-focus';
18
21
 
19
22
  export const STATE_LAYER_CLASSES = `${STATE_LAYER_OUTLINE} ${STATE_LAYER_BG_CLASSES}`;
20
23
 
21
- export const BORDER_CLASSES: Record<ChipState, string> = {
22
- [ChipState.Default]: 'chip-border-default',
23
- [ChipState.Selected]: 'border-transparent',
24
+ export const BG_SELECTED_OUTLINE: Partial<Record<ChipType, string>> = {
25
+ [ChipType.Outline]: 'chip-bg-selected-outline',
24
26
  };
25
27
 
26
- export const TEXT_COLOR: Record<ChipState, string> = {
27
- [ChipState.Default]: 'chip-text-default',
28
- [ChipState.Selected]: 'chip-text-selected',
28
+ export const BG_SELECTED_OVERLAY = 'chip-bg-selected-overlay';
29
+
30
+ export const TEXT_COLOR_SELECTED_OUTLINE: Partial<Record<ChipType, string>> = {
31
+ [ChipType.Outline]: 'chip-text-secondary',
32
+ };
33
+
34
+ export const BORDER_CLASSES: Record<ChipType, string> = {
35
+ [ChipType.Outline]: 'chip-border-default',
36
+ [ChipType.Elevated]: 'border-transparent',
37
+ [ChipType.Success]: 'border-success-surface',
38
+ [ChipType.Brand]: 'chip-border-color-brand',
39
+ [ChipType.Error]: 'border-error-surface',
40
+ };
41
+
42
+ export const TEXT_COLOR: Record<ChipType, string> = {
43
+ [ChipType.Outline]: 'chip-text-default',
44
+ [ChipType.Elevated]: 'chip-text-secondary',
45
+ [ChipType.Success]: 'chip-text-secondary',
46
+ [ChipType.Brand]: 'chip-text-brand',
47
+ [ChipType.Error]: 'chip-text-secondary',
29
48
  };
30
49
 
31
50
  export const FOCUS_CLASSES =
32
51
  'focus-visible:shadow-[0_0_0_2px] focus-visible:chip-focus-ring focus-visible:outline-none';
52
+
53
+ export const CLOSE_ICON_INTERACTIVE =
54
+ 'chip-close-icon-opacity group-hover:opacity-100 group-focus:opacity-100 group-active:opacity-100';
@@ -1,9 +1,12 @@
1
1
  // Copyright (c) 2024 IOTA Stiftung
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
- export enum ChipState {
5
- Default = 'default',
6
- Selected = 'selected',
4
+ export enum ChipType {
5
+ Outline = 'outline',
6
+ Elevated = 'elevated',
7
+ Success = 'success',
8
+ Brand = 'brand',
9
+ Error = 'error',
7
10
  }
8
11
 
9
12
  export enum ChipSize {
@@ -23,7 +23,7 @@ export interface BaseInputProps extends InputWrapperProps {
23
23
  /**
24
24
  * A leading icon that is shown before the input
25
25
  */
26
- leadingIcon?: React.JSX.Element;
26
+ leadingIcon?: React.ReactNode;
27
27
  /**
28
28
  * Supporting text that is shown at the end of the input component.
29
29
  */
@@ -35,7 +35,7 @@ export interface BaseInputProps extends InputWrapperProps {
35
35
  /**
36
36
  * Trailing element that is shown after the input
37
37
  */
38
- trailingElement?: React.JSX.Element;
38
+ trailingElement?: React.ReactNode;
39
39
  /**
40
40
  * Is the content of the input visible
41
41
  */
@@ -44,6 +44,10 @@ export interface BaseInputProps extends InputWrapperProps {
44
44
  * Value of the input
45
45
  */
46
46
  value?: string | number;
47
+ /**
48
+ * Text that is shown below the value of the input.
49
+ */
50
+ supportingValue?: string | null;
47
51
  /**
48
52
  * Default value of the input
49
53
  */
@@ -72,6 +76,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function InputComp
72
76
  trailingElement,
73
77
  isContentVisible,
74
78
  value,
79
+ supportingValue,
75
80
  defaultValue,
76
81
  onClearInput,
77
82
  isVisibilityToggleEnabled,
@@ -111,26 +116,38 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function InputComp
111
116
  required={inputProps.required}
112
117
  >
113
118
  <div
114
- className={cx('relative flex flex-row items-center gap-x-3', BORDER_CLASSES)}
119
+ className={cx(
120
+ 'input-container relative flex flex-row items-center gap-x-3',
121
+ BORDER_CLASSES,
122
+ )}
115
123
  onClick={focusOnInput}
116
124
  ref={inputWrapperRef}
117
125
  >
118
126
  {leadingIcon && <span className="input-icon-color">{leadingIcon}</span>}
119
- <InputElement
120
- {...inputProps}
121
- inputRef={forwardRef}
122
- value={value}
123
- type={
124
- type === InputType.Password && isInputContentVisible ? InputType.Text : type
125
- }
126
- disabled={disabled}
127
- className={cx(
128
- INPUT_CLASSES,
129
- INPUT_TEXT_CLASSES,
130
- INPUT_PLACEHOLDER_CLASSES,
131
- INPUT_NUMBER_CLASSES,
127
+ <div className="flex flex-1 flex-col items-start">
128
+ <InputElement
129
+ {...inputProps}
130
+ inputRef={forwardRef}
131
+ value={value}
132
+ type={
133
+ type === InputType.Password && isInputContentVisible
134
+ ? InputType.Text
135
+ : type
136
+ }
137
+ disabled={disabled}
138
+ className={cx(
139
+ INPUT_CLASSES,
140
+ INPUT_TEXT_CLASSES,
141
+ INPUT_PLACEHOLDER_CLASSES,
142
+ INPUT_NUMBER_CLASSES,
143
+ )}
144
+ />
145
+ {supportingValue && (
146
+ <span className="text-label-md text-iota-neutral-60 names:text-iota-neutral-60 dark:text-iota-neutral-40">
147
+ {supportingValue}
148
+ </span>
132
149
  )}
133
- />
150
+ </div>
134
151
 
135
152
  {supportingText && <SecondaryText>{supportingText}</SecondaryText>}
136
153
  <InputTrailingElement
@@ -62,6 +62,7 @@ export const TextArea = forwardRef<HTMLTextAreaElement, TextFieldBaseProps>(func
62
62
  isResizeEnabled,
63
63
  required,
64
64
  isContentVisible,
65
+ defaultValue,
65
66
  ...textareaProps
66
67
  },
67
68
  ref,
@@ -93,16 +94,16 @@ export const TextArea = forwardRef<HTMLTextAreaElement, TextFieldBaseProps>(func
93
94
  required={required}
94
95
  ref={ref}
95
96
  className={cx(
96
- 'peer block min-h-[50px]',
97
+ 'text-area-container peer block min-h-[50px]',
97
98
  BORDER_CLASSES,
98
99
  INPUT_CLASSES,
99
100
  INPUT_TEXT_CLASSES,
100
101
  INPUT_PLACEHOLDER_CLASSES,
101
102
  isInputContentVisible && isResizeEnabled ? 'resize-y' : 'resize-none',
102
- !isInputContentVisible &&
103
- 'not-visible select-none text-transparent dark:text-transparent',
103
+ !isInputContentVisible && 'not-visible select-none text-transparent',
104
104
  )}
105
- value={isInputContentVisible ? value : ''}
105
+ defaultValue={isInputContentVisible && defaultValue ? defaultValue : undefined}
106
+ value={isInputContentVisible && value ? value : undefined}
106
107
  {...textareaProps}
107
108
  />
108
109
  {!isInputContentVisible && (
@@ -10,7 +10,6 @@ export interface NavbarItemProps {
10
10
  * The type of the navbar item.
11
11
  */
12
12
  type?: NavbarItemType;
13
-
14
13
  /**
15
14
  * The icon of the navbar item.
16
15
  */
@@ -29,7 +29,7 @@ export function NavbarItemHorizontal({
29
29
  const backgroundColors = isSelected && SELECTED_BACKGROUND;
30
30
  const badgePositionClasses = text ? BADGE_WITH_TEXT : BADGE_WITHOUT_TEXT;
31
31
  const textClasses = isSelected ? SELECTED_TEXT : UNSELECTED_TEXT;
32
- const disabledClasses = isDisabled ? 'cursor-not-allowed opacity-60' : 'state-layer';
32
+ const disabledClasses = isDisabled ? 'cursor-not-allowed opacity-60' : 'state-layer-secondary';
33
33
  const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
34
34
  if (isDisabled) {
35
35
  e.preventDefault();
@@ -25,7 +25,7 @@ export function NavbarItemVertical({
25
25
  const textClasses = isSelected ? SELECTED_TEXT : UNSELECTED_TEXT;
26
26
  const disabledClasses = isDisabled
27
27
  ? 'cursor-not-allowed opacity-60'
28
- : 'state-layer cursor-pointer ';
28
+ : 'state-layer-secondary cursor-pointer ';
29
29
  const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
30
30
  if (isDisabled) {
31
31
  e.preventDefault();
@@ -3,7 +3,7 @@
3
3
 
4
4
  import { TriangleDown } from '@iota/apps-ui-icons';
5
5
  import cx from 'classnames';
6
- import { forwardRef, useEffect, useState } from 'react';
6
+ import { forwardRef, useEffect, useRef, useState } from 'react';
7
7
  import { Dropdown } from '../dropdown/Dropdown';
8
8
  import { SecondaryText } from '@/components/atoms/secondary-text';
9
9
  import { InputWrapper, LabelHtmlTag } from '../input/InputWrapper';
@@ -88,16 +88,35 @@ export const Select = forwardRef<HTMLButtonElement, SelectProps>(
88
88
  ) => {
89
89
  const [isOpen, setIsOpen] = useState<boolean>(false);
90
90
  const selectedValue = findValueByProps(value, options);
91
+ const wrapperRef = useRef<HTMLDivElement>(null);
91
92
 
92
93
  const selectorText = selectedValue || placeholder;
93
94
  const selectPadding = size === SelectSize.Small ? 'px-sm' : 'px-md';
94
95
  const textSize = size === SelectSize.Small ? 'text-body-md' : 'text-body-lg';
96
+
95
97
  useEffect(() => {
96
98
  if (disabled && isOpen) {
97
99
  closeDropdown();
98
100
  }
99
101
  }, [disabled, isOpen]);
100
102
 
103
+ useEffect(() => {
104
+ if (!isOpen) return;
105
+
106
+ const handleClickOutside = (e: MouseEvent) => {
107
+ const target = e.target as Node;
108
+ if (wrapperRef.current && !wrapperRef.current.contains(target)) {
109
+ closeDropdown();
110
+ }
111
+ };
112
+
113
+ document.addEventListener('mousedown', handleClickOutside);
114
+
115
+ return () => {
116
+ document.removeEventListener('mousedown', handleClickOutside);
117
+ };
118
+ }, [isOpen]);
119
+
101
120
  function findValueByProps(value: SelectProps['value'], options: SelectOption[] = []) {
102
121
  return (
103
122
  options.find((option) =>
@@ -123,6 +142,7 @@ export const Select = forwardRef<HTMLButtonElement, SelectProps>(
123
142
  function closeDropdown() {
124
143
  setIsOpen(false);
125
144
  }
145
+
126
146
  return (
127
147
  <InputWrapper
128
148
  label={label}
@@ -131,13 +151,13 @@ export const Select = forwardRef<HTMLButtonElement, SelectProps>(
131
151
  errorMessage={errorMessage}
132
152
  labelHtmlTag={LabelHtmlTag.Div}
133
153
  >
134
- <div className="relative flex w-full flex-col">
154
+ <div className="relative flex w-full flex-col" ref={wrapperRef}>
135
155
  <ButtonUnstyled
136
156
  ref={ref}
137
157
  onClick={onSelectorClick}
138
158
  disabled={disabled}
139
159
  className={cx(
140
- 'select-border-color focus-visible:enabled:select-border-focus-color active:enabled:select-border-focus-color group-[.errored]:select-border-error-color group-[.opened]:select-border-focus-color [&:is(:focus,_:focus-visible,_:active)]:enabled:select-border-focus-color hover:enabled:select-border-hover-color flex flex-row items-center gap-x-3 rounded-lg border py-sm disabled:cursor-not-allowed [&_svg]:h-5 [&_svg]:w-5',
160
+ 'select-container select-border-color focus-visible:enabled:select-border-focus-color active:enabled:select-border-focus-color group-[.errored]:select-border-error-color group-[.opened]:select-border-focus-color [&:is(:focus,_:focus-visible,_:active)]:enabled:select-border-focus-color hover:enabled:select-border-hover-color flex flex-row items-center gap-x-3 rounded-lg border py-sm disabled:cursor-not-allowed [&_svg]:h-5 [&_svg]:w-5',
141
161
  selectPadding,
142
162
  )}
143
163
  >
@@ -171,7 +191,7 @@ export const Select = forwardRef<HTMLButtonElement, SelectProps>(
171
191
 
172
192
  {isOpen && (
173
193
  <div
174
- className="fixed left-0 top-0 z-[49] h-screen w-screen bg-transparent"
194
+ className="fixed left-0 top-0 z-[49] bg-transparent"
175
195
  onClick={closeDropdown}
176
196
  />
177
197
  )}