@iota/apps-ui-kit 0.2.1 → 0.3.0

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 (28) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/index.d.ts +23 -9
  3. package/dist/index.es.js +4093 -3989
  4. package/dist/index.es.js.map +1 -1
  5. package/dist/index.umd.js +43 -43
  6. package/dist/index.umd.js.map +1 -1
  7. package/dist/style.css +1 -1
  8. package/package.json +3 -3
  9. package/src/lib/components/atoms/button/button.classes.ts +4 -4
  10. package/src/lib/components/atoms/info-box/infoBox.classes.ts +7 -2
  11. package/src/lib/components/atoms/info-box/infoBox.enums.ts +2 -0
  12. package/src/lib/components/atoms/snackbar/snackbar.classes.ts +5 -1
  13. package/src/lib/components/atoms/snackbar/snackbar.enums.tsx +2 -0
  14. package/src/lib/components/atoms/tooltip/Tooltip.tsx +4 -1
  15. package/src/lib/components/atoms/visual-asset-card/VisualAssetCard.tsx +26 -18
  16. package/src/lib/components/molecules/address/Address.tsx +1 -1
  17. package/src/lib/components/molecules/card/CardAction.tsx +4 -2
  18. package/src/lib/components/molecules/input/input.classes.ts +2 -2
  19. package/src/lib/components/molecules/search/Search.tsx +34 -3
  20. package/src/lib/components/molecules/table-header-cell/TableHeaderCell.tsx +27 -13
  21. package/src/lib/components/organisms/accordion/Accordion.tsx +3 -5
  22. package/src/lib/components/organisms/dialog/Dialog.tsx +6 -5
  23. package/src/lib/components/organisms/navbar/Navbar.tsx +10 -10
  24. package/src/lib/constants/colors.constants.ts +5 -0
  25. package/src/lib/styles/index.css +13 -0
  26. package/src/lib/tailwind/base.presets.ts +2 -0
  27. package/src/lib/tailwind/plugins.ts +8 -0
  28. package/src/storybook/stories/organisms/Navbar.stories.tsx +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iota/apps-ui-kit",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "github.com:iotaledger/iota.git"
@@ -30,7 +30,7 @@
30
30
  "react": "^18.3.1",
31
31
  "react-dom": "^18.3.1",
32
32
  "react-number-format": "^5.2.2",
33
- "@iota/apps-ui-icons": "0.2.1"
33
+ "@iota/apps-ui-icons": "0.2.2"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@chromatic-com/storybook": "^1.6.0",
@@ -54,7 +54,7 @@
54
54
  "storybook-dark-mode": "^4.0.2",
55
55
  "tailwindcss": "^3.3.3",
56
56
  "typescript": "^5.5.3",
57
- "vite": "^5.3.3",
57
+ "vite": "^5.4.12",
58
58
  "vite-plugin-dts": "^4.3.0",
59
59
  "vite-tsconfig-paths": "^4.2.0"
60
60
  },
@@ -18,7 +18,7 @@ export const BACKGROUND_COLORS: Record<ButtonType, string> = {
18
18
  [ButtonType.Secondary]: 'bg-neutral-90 dark:bg-neutral-20',
19
19
  [ButtonType.Ghost]: 'bg-transparent',
20
20
  [ButtonType.Outlined]: 'bg-transparent border border-neutral-50',
21
- [ButtonType.Destructive]: 'bg-error-90',
21
+ [ButtonType.Destructive]: 'bg-error-90 dark:bg-error-20',
22
22
  };
23
23
 
24
24
  export const DISABLED_BACKGROUND_COLORS: Record<ButtonType, string> = {
@@ -26,7 +26,7 @@ export const DISABLED_BACKGROUND_COLORS: Record<ButtonType, string> = {
26
26
  [ButtonType.Secondary]: 'bg-neutral-90 dark:bg-neutral-20',
27
27
  [ButtonType.Ghost]: 'bg-transparent',
28
28
  [ButtonType.Outlined]: 'bg-transparent border border-neutral-50',
29
- [ButtonType.Destructive]: 'bg-error-90',
29
+ [ButtonType.Destructive]: 'bg-error-90 dark:bg-error-20',
30
30
  };
31
31
 
32
32
  const DEFAULT_TEXT_COLORS: string = 'text-neutral-10 dark:text-neutral-92';
@@ -36,7 +36,7 @@ export const TEXT_COLORS: Record<ButtonType, string> = {
36
36
  [ButtonType.Secondary]: DEFAULT_TEXT_COLORS,
37
37
  [ButtonType.Ghost]: DEFAULT_TEXT_COLORS,
38
38
  [ButtonType.Outlined]: DEFAULT_TEXT_COLORS,
39
- [ButtonType.Destructive]: 'text-error-20',
39
+ [ButtonType.Destructive]: 'text-error-20 dark:text-error-90',
40
40
  };
41
41
 
42
42
  export const TEXT_CLASSES: Record<ButtonSize, string> = {
@@ -49,5 +49,5 @@ export const TEXT_COLOR_DISABLED: Record<ButtonType, string> = {
49
49
  [ButtonType.Secondary]: DEFAULT_TEXT_COLORS,
50
50
  [ButtonType.Ghost]: DEFAULT_TEXT_COLORS,
51
51
  [ButtonType.Outlined]: DEFAULT_TEXT_COLORS,
52
- [ButtonType.Destructive]: 'text-error-20',
52
+ [ButtonType.Destructive]: 'text-error-20 dark:text-error-90',
53
53
  };
@@ -4,11 +4,16 @@
4
4
  import { InfoBoxType } from './infoBox.enums';
5
5
 
6
6
  export const ICON_COLORS: Record<InfoBoxType, string> = {
7
- [InfoBoxType.Default]: 'bg-neutral-96 dark:bg-neutral-12 text-neutral-10 dark:text-neutral-92',
7
+ [InfoBoxType.Default]: 'bg-primary-90 dark:bg-primary-10 text-primary-20 dark:text-primary-90',
8
8
  [InfoBoxType.Error]: 'bg-error-90 dark:bg-error-10 text-error-20 dark:text-error-90',
9
+ [InfoBoxType.Success]:
10
+ 'bg-tertiary-90 dark:bg-tertiary-10 text-tertiary-20 dark:text-tertiary-90',
11
+ [InfoBoxType.Warning]: 'bg-warning-90 dark:bg-warning-20 text-warning-10 dark:text-warning-90',
9
12
  };
10
13
 
11
14
  export const BACKGROUND_COLORS: Record<InfoBoxType, string> = {
12
- [InfoBoxType.Default]: 'bg-neutral-96 dark:bg-neutral-12',
15
+ [InfoBoxType.Default]: 'bg-primary-90 dark:bg-primary-10',
13
16
  [InfoBoxType.Error]: 'bg-error-90 dark:bg-error-10',
17
+ [InfoBoxType.Success]: 'bg-tertiary-90 dark:bg-tertiary-10',
18
+ [InfoBoxType.Warning]: 'bg-warning-90 dark:bg-warning-20',
14
19
  };
@@ -4,6 +4,8 @@
4
4
  export enum InfoBoxType {
5
5
  Default = 'default',
6
6
  Error = 'error',
7
+ Success = 'success',
8
+ Warning = 'warning',
7
9
  }
8
10
 
9
11
  export enum InfoBoxStyle {
@@ -6,9 +6,13 @@ import { SnackbarType } from './snackbar.enums';
6
6
  export const TEXT_COLOR: Record<SnackbarType, string> = {
7
7
  [SnackbarType.Default]: 'text-neutral-10 dark:text-neutral-92',
8
8
  [SnackbarType.Error]: 'text-error-20 dark:text-error-90',
9
+ [SnackbarType.Warning]: 'text-warning-10 dark:text-warning-90',
10
+ [SnackbarType.Success]: 'text-tertiary-20 dark:text-tertiary-90',
9
11
  };
10
12
 
11
13
  export const BACKGROUND_COLOR: Record<SnackbarType, string> = {
12
- [SnackbarType.Default]: 'bg-neutral-80 dark:bg-neutral-30',
14
+ [SnackbarType.Default]: 'bg-primary-90 dark:bg-primary-10',
13
15
  [SnackbarType.Error]: 'bg-error-90 dark:bg-error-10',
16
+ [SnackbarType.Warning]: 'bg-warning-90 dark:bg-warning-20',
17
+ [SnackbarType.Success]: 'bg-tertiary-90 dark:bg-tertiary-10',
14
18
  };
@@ -4,4 +4,6 @@
4
4
  export enum SnackbarType {
5
5
  Default = 'default',
6
6
  Error = 'error',
7
+ Warning = 'warning',
8
+ Success = 'success',
7
9
  }
@@ -9,11 +9,13 @@ import { TOOLTIP_POSITION } from './tooltip.classes';
9
9
  interface TooltipProps {
10
10
  text: string;
11
11
  position?: TooltipPosition;
12
+ maxWidth?: string;
12
13
  }
13
14
 
14
15
  export function Tooltip({
15
16
  text,
16
17
  position = TooltipPosition.Top,
18
+ maxWidth = 'max-w-[200px]',
17
19
  children,
18
20
  }: PropsWithChildren<TooltipProps>): React.JSX.Element {
19
21
  const tooltipPositionClass = TOOLTIP_POSITION[position];
@@ -22,8 +24,9 @@ export function Tooltip({
22
24
  {children}
23
25
  <div
24
26
  className={cx(
25
- 'absolute z-[999] hidden w-max max-w-[200px] rounded bg-neutral-80 p-xs text-neutral-10 opacity-0 transition-opacity duration-300 group-hover:block group-hover:opacity-100 group-focus:opacity-100 dark:bg-neutral-30 dark:text-neutral-92',
27
+ 'absolute z-[999] hidden w-max rounded bg-neutral-80 p-xs text-neutral-10 opacity-0 transition-opacity duration-300 group-hover:block group-hover:opacity-100 group-focus:opacity-100 dark:bg-neutral-30 dark:text-neutral-92',
26
28
  tooltipPositionClass,
29
+ maxWidth,
27
30
  )}
28
31
  role="tooltip"
29
32
  >
@@ -3,8 +3,9 @@
3
3
 
4
4
  import { VisualAssetType } from './visualAssetCard.enums';
5
5
  import { ButtonUnstyled } from '../button';
6
- import { MoreHoriz } from '@iota/apps-ui-icons';
6
+ import { MoreHoriz, PlaceholderReplace } from '@iota/apps-ui-icons';
7
7
  import cx from 'classnames';
8
+ import { useState } from 'react';
8
9
 
9
10
  export interface VisualAssetCardProps {
10
11
  /**
@@ -51,6 +52,8 @@ export function VisualAssetCard({
51
52
  assetTitle,
52
53
  isHoverable = true,
53
54
  }: VisualAssetCardProps): React.JSX.Element {
55
+ const [imageError, setImageError] = useState(false);
56
+
54
57
  const handleIconClick = (event: React.MouseEvent<HTMLButtonElement>) => {
55
58
  onIconClick?.(event);
56
59
  event?.stopPropagation();
@@ -65,28 +68,33 @@ export function VisualAssetCard({
65
68
  >
66
69
  {assetType === VisualAssetType.Video ? (
67
70
  <video src={assetSrc} className="h-full w-full object-cover" autoPlay loop muted />
71
+ ) : imageError ? (
72
+ <div className="flex h-full w-full items-center justify-center bg-neutral-96 dark:bg-neutral-10">
73
+ <PlaceholderReplace className="h-4 w-4 text-neutral-40 dark:text-neutral-60" />
74
+ </div>
68
75
  ) : (
69
- <img src={assetSrc} alt={altText} className="h-full w-full object-cover" />
76
+ <img
77
+ src={assetSrc}
78
+ alt={altText}
79
+ className="h-full w-full object-cover"
80
+ onError={() => setImageError(true)}
81
+ />
70
82
  )}
71
83
  {isHoverable && (
72
84
  <div className="absolute left-0 top-0 h-full w-full bg-cover bg-center bg-no-repeat group-hover:bg-shader-neutral-light-48 group-hover:transition group-hover:duration-300 group-hover:ease-in-out group-hover:dark:bg-shader-primary-dark-48" />
73
85
  )}
74
- {isHoverable && (
75
- <>
76
- {onIconClick && (
77
- <ButtonUnstyled
78
- className="absolute right-2 top-2 h-9 w-9 cursor-pointer rounded-full p-xs opacity-0 transition-opacity duration-300 group-hover:bg-shader-neutral-light-72 group-hover:opacity-100 [&_svg]:h-5 [&_svg]:w-5 [&_svg]:text-primary-100"
79
- onClick={handleIconClick}
80
- >
81
- {icon}
82
- </ButtonUnstyled>
83
- )}
84
- <div className="absolute bottom-0 flex items-center justify-center p-xs opacity-0 transition-opacity duration-300 group-hover:opacity-100">
85
- {assetTitle && (
86
- <span className="text-title-md text-neutral-100">{assetTitle}</span>
87
- )}
88
- </div>
89
- </>
86
+ {isHoverable && onIconClick && (
87
+ <ButtonUnstyled
88
+ className="absolute right-2 top-2 h-9 w-9 cursor-pointer rounded-full p-xs opacity-0 transition-opacity duration-300 group-hover:bg-shader-neutral-light-72 group-hover:opacity-100 [&_svg]:h-5 [&_svg]:w-5 [&_svg]:text-primary-100"
89
+ onClick={handleIconClick}
90
+ >
91
+ {icon}
92
+ </ButtonUnstyled>
93
+ )}
94
+ {isHoverable && assetTitle && (
95
+ <div className="absolute bottom-0 flex items-center justify-center p-xs opacity-0 transition-opacity duration-300 group-hover:opacity-100">
96
+ <span className="text-title-md text-neutral-100">{assetTitle}</span>
97
+ </div>
90
98
  )}
91
99
  </div>
92
100
  );
@@ -70,7 +70,7 @@ export function Address({
70
70
  function handleOpenClick(event: React.MouseEvent<HTMLButtonElement>) {
71
71
  event?.stopPropagation();
72
72
  if (externalLink) {
73
- const newWindow = window.open(externalLink, '_blank', 'noopener,noreferrer');
73
+ const newWindow = window.open(externalLink, '_blank', 'noopener noreferrer');
74
74
  if (newWindow) newWindow.opener = null;
75
75
  } else {
76
76
  onOpen?.(event);
@@ -27,8 +27,10 @@ export function CardAction({
27
27
  buttonDisabled,
28
28
  }: CardActionProps) {
29
29
  function handleActionClick(event: React.MouseEvent) {
30
- event?.stopPropagation();
31
- onClick?.();
30
+ if (onClick) {
31
+ event.stopPropagation();
32
+ onClick();
33
+ }
32
34
  }
33
35
 
34
36
  if (type === CardActionType.Link) {
@@ -5,9 +5,9 @@ export const INPUT_CLASSES =
5
5
  'w-full bg-transparent text-body-lg caret-primary-30 dark:caret-primary-80 focus:outline-none focus-visible:outline-none disabled:cursor-not-allowed';
6
6
  export const INPUT_TEXT_CLASSES = 'text-neutral-10 dark:text-neutral-92';
7
7
  export const INPUT_PLACEHOLDER_CLASSES =
8
- ' enabled:placeholder:text-neutral-40/40 dark:placeholder:text-neutral-60/40 enabled:dark:placeholder:text-neutral-60/40';
8
+ 'enabled:placeholder:text-neutral-40/40 dark:placeholder:text-neutral-60/40 enabled:dark:placeholder:text-neutral-60/40';
9
9
  export const BORDER_CLASSES =
10
- 'px-md py-sm rounded-lg border border-neutral-80 group-[.enabled]:cursor-text group-[.errored]:border-error-30 hover:group-[.enabled]:border-neutral-50 dark:border-neutral-60 dark:hover:border-neutral-60 dark:group-[.errored]:border-error-80 [&:has(input:focus)]:border-primary-30 [&:has(input:focus)]:dark:border-primary-80';
10
+ 'px-md py-sm rounded-lg border border-neutral-80 group-[.enabled]:cursor-text group-[.errored]:border-error-30 hover:group-[.enabled]:border-neutral-50 dark:border-neutral-20 dark:hover:border-neutral-60 dark:group-[.errored]:border-error-80 [&:has(input:focus)]:!border-primary-30 [&:has(input:focus)]:dark:!border-primary-80';
11
11
  export const INPUT_NUMBER_CLASSES =
12
12
  '[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none disabled:cursor-not-allowed';
13
13
  export const LABEL_CLASSES =
@@ -71,6 +71,7 @@ export function Search({
71
71
  const inputRef = useRef<HTMLInputElement>(null);
72
72
  const suggestionsListRef = useRef<HTMLDivElement>(null);
73
73
  const [isSuggestionsVisible, setIsSuggestionsVisible] = useState(true);
74
+ const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
74
75
 
75
76
  function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
76
77
  const value = event.target.value;
@@ -131,9 +132,33 @@ export function Search({
131
132
  onSuggestionClick?.(suggestion);
132
133
  onSearchValueChange('');
133
134
  setIsSuggestionsVisible(false);
135
+ setSelectedIndex(null);
134
136
  inputRef.current?.blur();
135
137
  };
136
138
 
139
+ const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
140
+ if (suggestions && suggestions?.length > 0) {
141
+ if (event.key === 'ArrowDown') {
142
+ event.preventDefault();
143
+ setSelectedIndex((prev) =>
144
+ prev === null || prev >= suggestions.length - 1 ? 0 : prev + 1,
145
+ );
146
+ } else if (event.key === 'ArrowUp') {
147
+ event.preventDefault();
148
+ setSelectedIndex((prev) =>
149
+ prev === null || prev <= 0 ? suggestions.length - 1 : prev - 1,
150
+ );
151
+ } else if (event.key === 'Enter') {
152
+ event.preventDefault();
153
+ if (selectedIndex !== null && suggestions[selectedIndex]) {
154
+ handleOnSuggestionClick(suggestions[selectedIndex]);
155
+ } else if (suggestions.length === 1) {
156
+ handleOnSuggestionClick(suggestions[0]);
157
+ }
158
+ }
159
+ }
160
+ };
161
+
137
162
  return (
138
163
  <div className="relative w-full">
139
164
  <div
@@ -148,7 +173,7 @@ export function Search({
148
173
  type="text"
149
174
  value={searchValue}
150
175
  onChange={handleChange}
151
- onKeyDown={onKeyDown}
176
+ onKeyDown={handleKeyDown}
152
177
  onFocus={() => setIsSuggestionsVisible(true)}
153
178
  placeholder={placeholder}
154
179
  className={cx(
@@ -168,15 +193,21 @@ export function Search({
168
193
  >
169
194
  <Divider width="w-11/12" />
170
195
  {isLoading ? (
171
- <div className=" px-md py-sm">
196
+ <div className="px-md py-sm">
172
197
  <Loader className="animate-spin" />
173
198
  </div>
174
199
  ) : (
175
200
  suggestions.map((suggestion, index) => (
176
201
  <div
177
- className="w-full"
178
202
  key={suggestion.id}
179
203
  onClick={() => handleOnSuggestionClick(suggestion)}
204
+ onMouseEnter={() => setSelectedIndex(index)}
205
+ className={cx(
206
+ 'w-full cursor-pointer px-md py-sm',
207
+ selectedIndex === index
208
+ ? 'bg-shader-primary-dark-16 dark:bg-shader-inverted-dark-16'
209
+ : '',
210
+ )}
180
211
  >
181
212
  {renderSuggestion(suggestion, index)}
182
213
  </div>
@@ -1,7 +1,6 @@
1
1
  // Copyright (c) 2024 IOTA Stiftung
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
- import { useState } from 'react';
5
4
  import { SortByDown, SortByUp } from '@iota/apps-ui-icons';
6
5
  import cx from 'classnames';
7
6
  import { Checkbox } from '@/lib';
@@ -52,6 +51,10 @@ export interface TableHeaderCellProps {
52
51
  * Whether the cell content should be centered.
53
52
  */
54
53
  isContentCentered?: boolean;
54
+ /**
55
+ * Sort order when cell is initialized
56
+ */
57
+ sortOrder?: TableHeaderCellSortOrder;
55
58
  }
56
59
 
57
60
  export function TableHeaderCell({
@@ -64,17 +67,13 @@ export function TableHeaderCell({
64
67
  isContentCentered,
65
68
  onSortClick,
66
69
  onCheckboxChange,
70
+ sortOrder,
67
71
  }: TableHeaderCellProps): JSX.Element {
68
- const [sortOrder, setSortOrder] = useState<TableHeaderCellSortOrder | null>(
69
- TableHeaderCellSortOrder.Asc,
70
- );
71
-
72
72
  const handleSort = () => {
73
73
  const newSortOrder =
74
74
  sortOrder === TableHeaderCellSortOrder.Asc
75
75
  ? TableHeaderCellSortOrder.Desc
76
76
  : TableHeaderCellSortOrder.Asc;
77
- setSortOrder(newSortOrder);
78
77
  if (onSortClick) {
79
78
  onSortClick(columnKey, newSortOrder);
80
79
  }
@@ -83,10 +82,30 @@ export function TableHeaderCell({
83
82
  const textColorClass = 'text-neutral-10 dark:text-neutral-92';
84
83
  const textSizeClass = 'text-label-lg';
85
84
 
85
+ const sortElement = (() => {
86
+ if (!hasSort) {
87
+ return null;
88
+ }
89
+
90
+ if (sortOrder === TableHeaderCellSortOrder.Asc) {
91
+ return <SortByUp className="shrink-0" />;
92
+ }
93
+
94
+ if (sortOrder === TableHeaderCellSortOrder.Desc) {
95
+ return <SortByDown className="shrink-0" />;
96
+ }
97
+
98
+ return <SortByUp className="invisible shrink-0 group-hover:visible" />;
99
+ })();
100
+
86
101
  return (
87
102
  <th
103
+ onClick={hasSort ? handleSort : undefined}
88
104
  className={cx(
89
- 'state-layer relative h-14 border-b border-shader-neutral-light-8 px-md after:pointer-events-none dark:border-shader-neutral-dark-8',
105
+ 'state-layer group relative h-14 border-b border-shader-neutral-light-8 px-md after:pointer-events-none dark:border-shader-neutral-dark-8',
106
+ {
107
+ 'cursor-pointer': hasSort,
108
+ },
90
109
  )}
91
110
  >
92
111
  <div
@@ -114,12 +133,7 @@ export function TableHeaderCell({
114
133
  {label}
115
134
  </span>
116
135
  )}
117
- {hasSort && sortOrder === TableHeaderCellSortOrder.Asc && (
118
- <SortByUp className="cursor-pointer" onClick={handleSort} />
119
- )}
120
- {hasSort && sortOrder === TableHeaderCellSortOrder.Desc && (
121
- <SortByDown className="cursor-pointer" onClick={handleSort} />
122
- )}
136
+ {sortElement}
123
137
  </div>
124
138
  </th>
125
139
  );
@@ -44,16 +44,14 @@ export function AccordionHeader({
44
44
  children,
45
45
  isExpanded,
46
46
  hideArrow,
47
- hideBorder,
48
47
  }: PropsWithChildren<AccordionHeaderProps>) {
49
48
  return (
50
49
  <div
51
50
  onClick={onToggle}
52
51
  className={cx(
53
- 'state-layer relative flex cursor-pointer items-center justify-between gap-md py-sm--rs',
52
+ 'state-layer relative flex cursor-pointer items-center justify-between gap-md overflow-hidden rounded-xl py-sm--rs',
54
53
  {
55
54
  'pr-md--rs': !hideArrow,
56
- 'rounded-xl': hideBorder,
57
55
  },
58
56
  )}
59
57
  >
@@ -100,8 +98,8 @@ export function Accordion({
100
98
  }): React.JSX.Element {
101
99
  return (
102
100
  <div
103
- className={cx('overflow-hidden rounded-xl bg-neutral-100 dark:bg-neutral-6', {
104
- ' border border-shader-neutral-light-8 dark:border-shader-neutral-dark-8':
101
+ className={cx('rounded-xl bg-neutral-100 dark:bg-neutral-6', {
102
+ 'border border-shader-neutral-light-8 dark:border-shader-neutral-dark-8':
105
103
  !hideBorder,
106
104
  })}
107
105
  >
@@ -68,17 +68,18 @@ const DialogContent = React.forwardRef<
68
68
  }, [containerId]);
69
69
  const positionClass =
70
70
  position === DialogPosition.Right
71
- ? 'right-0 h-screen top-0 w-full'
72
- : 'max-h-[60vh] left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-xl';
71
+ ? 'overflow-hidden right-0 h-screen top-0 w-full'
72
+ : 'overflow-y-auto overflow-x-hidden left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-xl';
73
73
  const widthClass =
74
74
  position === DialogPosition.Right ? 'md:w-96 max-w-[500px]' : customWidth;
75
+ const heightClass = position === DialogPosition.Right ? 'h-screen' : 'max-h-[80vh] h-full';
75
76
  return (
76
77
  <RadixDialog.Portal container={containerElement}>
77
78
  <DialogOverlay showCloseIcon={showCloseOnOverlay} position={position} />
78
79
  <RadixDialog.Content
79
80
  ref={ref}
80
81
  className={cx(
81
- 'fixed z-[99999] flex flex-col justify-center overflow-hidden bg-primary-100 dark:bg-neutral-6',
82
+ 'fixed z-[99999] flex flex-col justify-center bg-primary-100 dark:bg-neutral-6',
82
83
  positionClass,
83
84
  widthClass,
84
85
  )}
@@ -88,7 +89,7 @@ const DialogContent = React.forwardRef<
88
89
  <RadixDialog.Title />
89
90
  <RadixDialog.Description />
90
91
  </VisuallyHidden.Root>
91
- {children}
92
+ <div className={cx('flex flex-1 flex-col', heightClass)}>{children}</div>
92
93
  </RadixDialog.Content>
93
94
  </RadixDialog.Portal>
94
95
  );
@@ -112,7 +113,7 @@ const DialogBody = React.forwardRef<React.ElementRef<'div'>, React.ComponentProp
112
113
  (props, ref) => (
113
114
  <div
114
115
  ref={ref}
115
- className="p-md--rs text-body-sm text-neutral-40 dark:text-neutral-60"
116
+ className="flex-1 overflow-y-auto p-md--rs text-body-sm text-neutral-40 dark:text-neutral-60"
116
117
  {...props}
117
118
  />
118
119
  ),
@@ -9,9 +9,9 @@ export type NavbarItemWithId = NavbarItemProps & { id: string };
9
9
 
10
10
  export interface NavbarProps {
11
11
  /**
12
- * If this flag is true we need to leave only the icon and collapsable button
12
+ * If this flag is true we need to leave only the icon and collapsible button
13
13
  */
14
- isCollapsable?: boolean;
14
+ isCollapsible?: boolean;
15
15
 
16
16
  /**
17
17
  * List of elements to be displayed in the navbar.
@@ -29,7 +29,7 @@ export interface NavbarProps {
29
29
  onClickItem: (id: string) => void;
30
30
 
31
31
  /**
32
- * If the navbar is collapsable, this flag indicates if it is open or not.
32
+ * If the navbar is collapsible, this flag indicates if it is open or not.
33
33
  */
34
34
  isOpen?: boolean;
35
35
 
@@ -43,16 +43,16 @@ export function Navbar({
43
43
  items,
44
44
  activeId,
45
45
  onClickItem,
46
- isCollapsable = false,
46
+ isCollapsible = false,
47
47
  onToggleNavbar,
48
48
  }: NavbarProps) {
49
49
  return (
50
50
  <div
51
51
  className={cx('flex h-fit w-full', {
52
- 'flex-col px-md py-xs sm:h-full sm:w-auto sm:px-none sm:py-xl': isCollapsable,
52
+ 'flex-col px-md py-xs sm:h-full sm:w-auto sm:px-none sm:py-xl': isCollapsible,
53
53
  })}
54
54
  >
55
- {isCollapsable && (
55
+ {isCollapsible && (
56
56
  <div className="flex w-full items-center justify-between sm:mb-[48px] sm:flex-col">
57
57
  <div className="flex justify-center [&_svg]:h-[38px] [&_svg]:w-[38px]">
58
58
  <IotaLogoMark className="text-neutral-10 dark:text-neutral-92" />
@@ -67,16 +67,16 @@ export function Navbar({
67
67
  )}
68
68
  <div
69
69
  className={cx({
70
- 'flex w-full justify-between px-sm py-xxs': !isCollapsable,
71
- 'hidden sm:flex sm:flex-col sm:gap-2': isCollapsable,
70
+ 'flex w-full justify-between px-sm py-xxs': !isCollapsible,
71
+ 'hidden sm:flex sm:flex-col sm:gap-2': isCollapsible,
72
72
  })}
73
73
  >
74
74
  {items.map((item) => (
75
75
  <div
76
76
  key={item.id}
77
77
  className={cx('flex items-center', {
78
- 'px-xs py-xxs': !isCollapsable,
79
- 'py-xxs pl-xs pr-sm': isCollapsable,
78
+ 'px-xs py-xxs': !isCollapsible,
79
+ 'py-xxs pl-xs pr-sm': isCollapsible,
80
80
  })}
81
81
  data-testid={`nav-${item.id}`}
82
82
  >
@@ -73,6 +73,11 @@ export const IOTA_PRIMITIVES_COLOR_PALETTE = {
73
73
  '98': '#f8fafc',
74
74
  '100': '#ffffff',
75
75
  },
76
+ warning: {
77
+ '10': '#422C00',
78
+ '20': '#2D1E00',
79
+ '90': '#FFE3AB',
80
+ },
76
81
  };
77
82
 
78
83
  export const SHADER_COLOR_PALETTE = {
@@ -120,6 +120,19 @@
120
120
  }
121
121
  }
122
122
 
123
+ @-moz-document url-prefix() {
124
+ * {
125
+ scrollbar-width: thin;
126
+ scrollbar-color: theme('colors.neutral.30') transparent;
127
+ }
128
+
129
+ :root:not(.dark) {
130
+ * {
131
+ scrollbar-color: theme('colors.neutral.80') transparent;
132
+ }
133
+ }
134
+ }
135
+
123
136
  @supports selector(::-webkit-scrollbar) {
124
137
  *::-webkit-scrollbar {
125
138
  @apply w-3.5;
@@ -10,10 +10,12 @@ import {
10
10
  OPACITY,
11
11
  generateVariableSpacing,
12
12
  } from './constants';
13
+ import { firefoxPlugin } from './plugins';
13
14
 
14
15
  export const BASE_CONFIG: Config = {
15
16
  content: ['./src/**/*.{html,js,jsx,ts,tsx}'],
16
17
  darkMode: 'class',
18
+ plugins: [firefoxPlugin],
17
19
  theme: {
18
20
  extend: {
19
21
  fontSize: {
@@ -0,0 +1,8 @@
1
+ // Copyright (c) 2024 IOTA Stiftung
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import plugin from 'tailwindcss/plugin';
5
+
6
+ export const firefoxPlugin = plugin(({ addVariant }) => {
7
+ addVariant('firefox', '@-moz-document url-prefix()');
8
+ });
@@ -47,7 +47,7 @@ type Story = StoryObj<typeof meta>;
47
47
 
48
48
  export const Default: Story = {};
49
49
 
50
- export const Collapsable: Story = {
50
+ export const Collapsible: Story = {
51
51
  args: {},
52
52
  argTypes: {},
53
53
  render: (args) => {
@@ -57,7 +57,7 @@ export const Collapsable: Story = {
57
57
  return (
58
58
  <div className="flex h-96">
59
59
  <Navbar
60
- isCollapsable
60
+ isCollapsible
61
61
  items={NAVBAR_ITEMS}
62
62
  activeId={activeId}
63
63
  onClickItem={(id) => setActiveId(id)}