@iota/apps-ui-kit 0.3.0 → 0.5.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iota/apps-ui-kit",
3
- "version": "0.3.0",
3
+ "version": "0.5.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.2"
33
+ "@iota/apps-ui-icons": "0.3.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@chromatic-com/storybook": "^1.6.0",
@@ -7,18 +7,27 @@ export const BACKGROUND_COLORS = {
7
7
  [BadgeType.PrimarySolid]: 'bg-primary-30',
8
8
  [BadgeType.Neutral]: 'bg-neutral-92 dark:bg-neutral-12',
9
9
  [BadgeType.PrimarySoft]: 'bg-primary-90 dark:bg-primary-10',
10
+ [BadgeType.Success]: 'bg-tertiary-90 dark:bg-tertiary-10',
11
+ [BadgeType.Warning]: 'bg-warning-90 dark:bg-warning-20',
12
+ [BadgeType.Error]: 'bg-error-90 dark:bg-error-10',
10
13
  };
11
14
 
12
15
  export const TEXT_COLORS: Record<BadgeType, string> = {
13
16
  [BadgeType.PrimarySolid]: 'text-primary-100',
14
17
  [BadgeType.Neutral]: 'text-neutral-10 dark:text-neutral-92',
15
18
  [BadgeType.PrimarySoft]: 'text-primary-20 dark:text-primary-90',
19
+ [BadgeType.Success]: 'text-tertiary-20 dark:text-tertiary-90',
20
+ [BadgeType.Warning]: 'text-warning-10 dark:text-warning-90',
21
+ [BadgeType.Error]: 'text-error-20 dark:text-error-90',
16
22
  };
17
23
 
18
24
  export const BORDER_COLORS: Record<BadgeType, string> = {
19
25
  [BadgeType.PrimarySolid]: 'border-primary-30',
20
26
  [BadgeType.Neutral]: 'border-neutral-92 dark:border-neutral-12',
21
27
  [BadgeType.PrimarySoft]: 'border-primary-90 dark:border-primary-10',
28
+ [BadgeType.Success]: 'border-tertiary-90 dark:border-tertiary-10',
29
+ [BadgeType.Warning]: 'border-warning-90 dark:border-warning-20',
30
+ [BadgeType.Error]: 'border-error-90 dark:border-error-10',
22
31
  };
23
32
 
24
33
  export const BADGE_TEXT_CLASS = 'text-label-md';
@@ -5,4 +5,7 @@ export enum BadgeType {
5
5
  Neutral = 'neutral',
6
6
  PrimarySolid = 'primary-solid',
7
7
  PrimarySoft = 'primary-fill',
8
+ Success = 'success',
9
+ Warning = 'warning',
10
+ Error = 'error',
8
11
  }
@@ -20,3 +20,4 @@ export * from './visual-asset-card';
20
20
  export * from './loading-indicator';
21
21
  export * from './placeholder';
22
22
  export * from './skeleton';
23
+ export * from './toggle';
@@ -17,7 +17,7 @@ export interface InfoBoxProps {
17
17
  /**
18
18
  * The supporting text of the info box (optional).
19
19
  */
20
- supportingText?: string;
20
+ supportingText?: React.ReactNode;
21
21
  /**
22
22
  * The type of the info box.
23
23
  */
@@ -0,0 +1,140 @@
1
+ // Copyright (c) 2025 IOTA Stiftung
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { forwardRef, useRef } from 'react';
5
+ import { ToggleLabelPosition, ToggleSize } from './toggle.enums';
6
+ import {
7
+ TOGGLE,
8
+ TOGGLE_LABEL,
9
+ TOGGLE_SIZE,
10
+ TOGGLE_STATES,
11
+ TOGGLE_THUMB,
12
+ TOGGLE_THUMB_COLOR,
13
+ TOGGLE_THUMB_POSITION,
14
+ TOGGLE_THUMB_SIZE,
15
+ TOGGLE_CONTAINER,
16
+ } from './toggle.classes';
17
+ import cx from 'classnames';
18
+
19
+ interface ToggleProps {
20
+ /**
21
+ * The label for the toggle.
22
+ */
23
+ label?: string | React.ReactNode;
24
+ /**
25
+ * The state of the toggle (on or off).
26
+ */
27
+ isToggled: boolean;
28
+ /**
29
+ * Whether the label should be placed before the toggle.
30
+ */
31
+ labelPosition?: ToggleLabelPosition;
32
+ /**
33
+ * If true, the toggle will be disabled.
34
+ */
35
+ isDisabled?: boolean;
36
+ /**
37
+ * The callback to call when the toggle state changes.
38
+ */
39
+ onChange?: (isToggled: boolean, event: React.ChangeEvent<HTMLInputElement>) => void;
40
+ /**
41
+ * The name and id of the toggle input.
42
+ */
43
+ name?: string;
44
+ /**
45
+ * The size of the toggle.
46
+ */
47
+ size?: ToggleSize;
48
+ /**
49
+ * The 'data-testid' attribute value (used in e2e tests)
50
+ */
51
+ testId?: string;
52
+ }
53
+
54
+ export const Toggle = forwardRef<HTMLInputElement, ToggleProps>(
55
+ (
56
+ {
57
+ label,
58
+ isToggled,
59
+ labelPosition = ToggleLabelPosition.Right,
60
+ isDisabled = false,
61
+ onChange,
62
+ name,
63
+ size = ToggleSize.Default,
64
+ testId,
65
+ }: ToggleProps,
66
+ ref,
67
+ ) => {
68
+ const inputRef = useRef<HTMLInputElement | null>(null);
69
+
70
+ function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
71
+ const newChecked = e.target.checked;
72
+ onChange?.(newChecked, e);
73
+ }
74
+
75
+ function assignRefs(element: HTMLInputElement) {
76
+ if (ref) {
77
+ if (typeof ref === 'function') {
78
+ ref(element);
79
+ } else {
80
+ ref.current = element;
81
+ }
82
+ }
83
+ inputRef.current = element;
84
+ }
85
+
86
+ const toggleClasses = cx(TOGGLE, {
87
+ [TOGGLE_STATES.active]: isToggled && !isDisabled,
88
+ [TOGGLE_STATES.inactive]: !isToggled,
89
+ [TOGGLE_STATES.disabledActive]: isDisabled && isToggled,
90
+ [TOGGLE_STATES.disabled]: isDisabled,
91
+ [TOGGLE_SIZE[size]]: true,
92
+ });
93
+
94
+ const thumbClasses = cx(TOGGLE_THUMB, {
95
+ [TOGGLE_THUMB_POSITION[size].unchecked]: !isToggled,
96
+ [TOGGLE_THUMB_POSITION[size].checked]: isToggled,
97
+ [TOGGLE_THUMB_COLOR.unchecked]: !isToggled,
98
+ [TOGGLE_THUMB_COLOR.checked]: isToggled,
99
+ [TOGGLE_THUMB_SIZE[size]]: true,
100
+ });
101
+
102
+ const containerClasses = cx(TOGGLE_CONTAINER, {
103
+ 'flex-row-reverse': labelPosition === ToggleLabelPosition.Left,
104
+ 'cursor-not-allowed': isDisabled,
105
+ });
106
+ const labelClasses = cx(TOGGLE_LABEL, {
107
+ 'opacity-40': isDisabled && !isToggled,
108
+ });
109
+
110
+ return (
111
+ <div className={containerClasses}>
112
+ <input
113
+ id={name}
114
+ name={name}
115
+ type="checkbox"
116
+ className="sr-only"
117
+ checked={isToggled}
118
+ ref={assignRefs}
119
+ disabled={isDisabled}
120
+ onChange={handleChange}
121
+ data-testid={testId}
122
+ />
123
+
124
+ <span
125
+ role="switch"
126
+ onClick={() => inputRef.current?.click()}
127
+ className={toggleClasses}
128
+ >
129
+ <span className={thumbClasses} />
130
+ </span>
131
+
132
+ {label && (
133
+ <label htmlFor={name} className={labelClasses}>
134
+ {label}
135
+ </label>
136
+ )}
137
+ </div>
138
+ );
139
+ },
140
+ );
@@ -0,0 +1,6 @@
1
+ // Copyright (c) 2025 IOTA Stiftung
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ export { Toggle } from './Toggle';
5
+
6
+ export * from './toggle.enums';
@@ -0,0 +1,41 @@
1
+ // Copyright (c) 2025 IOTA Stiftung
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { ToggleSize } from './toggle.enums';
5
+
6
+ export const TOGGLE = 'relative inline-flex items-center p-xxs border rounded-full cursor-pointer';
7
+
8
+ export const TOGGLE_STATES = {
9
+ active: 'bg-primary-30 border-primary-30',
10
+ inactive: 'bg-primary-100 dark:bg-neutral-6 border-neutral-70 dark:border-neutral-40',
11
+ disabledActive: 'bg-neutral-70 border-neutral-70',
12
+ disabled: 'opacity-40 cursor-not-allowed',
13
+ };
14
+
15
+ export const TOGGLE_SIZE = {
16
+ [ToggleSize.Small]: 'h-5 w-10',
17
+ [ToggleSize.Default]: 'h-6 w-12',
18
+ };
19
+
20
+ export const TOGGLE_THUMB = 'absolute rounded-full transition-all duration-200 ease-in';
21
+ export const TOGGLE_THUMB_POSITION = {
22
+ [ToggleSize.Small]: {
23
+ unchecked: 'left-1',
24
+ checked: 'left-8 -translate-x-full',
25
+ },
26
+ [ToggleSize.Default]: {
27
+ unchecked: 'left-1',
28
+ checked: 'left-10 -translate-x-full',
29
+ },
30
+ };
31
+ export const TOGGLE_THUMB_COLOR = {
32
+ unchecked: 'bg-neutral-60',
33
+ checked: 'bg-white',
34
+ };
35
+ export const TOGGLE_THUMB_SIZE = {
36
+ [ToggleSize.Small]: 'h-3 w-3',
37
+ [ToggleSize.Default]: 'h-4 w-4',
38
+ };
39
+
40
+ export const TOGGLE_CONTAINER = 'inline-flex items-center gap-2';
41
+ export const TOGGLE_LABEL = 'text-label-lg text-neutral-40 dark:text-neutral-60';
@@ -0,0 +1,12 @@
1
+ // Copyright (c) 2025 IOTA Stiftung
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ export enum ToggleSize {
5
+ Small = 'small',
6
+ Default = 'default',
7
+ }
8
+
9
+ export enum ToggleLabelPosition {
10
+ Left = 'left',
11
+ Right = 'right',
12
+ }
@@ -3,6 +3,7 @@
3
3
  import cx from 'classnames';
4
4
 
5
5
  import { ReactNode } from 'react';
6
+ import { Tooltip, TooltipPosition } from '../../atoms';
6
7
 
7
8
  export type CardBodyProps = {
8
9
  title: string;
@@ -10,6 +11,8 @@ export type CardBodyProps = {
10
11
  clickableAction?: React.ReactNode;
11
12
  icon?: React.ReactNode;
12
13
  isTextTruncated?: boolean;
14
+ tooltipText?: string;
15
+ tooltipPosition?: TooltipPosition;
13
16
  };
14
17
 
15
18
  export function CardBody({
@@ -18,6 +21,8 @@ export function CardBody({
18
21
  clickableAction,
19
22
  icon,
20
23
  isTextTruncated,
24
+ tooltipText,
25
+ tooltipPosition,
21
26
  }: CardBodyProps) {
22
27
  const handleActionCardBodyClick = (event: React.MouseEvent) => {
23
28
  event?.stopPropagation();
@@ -40,7 +45,13 @@ export function CardBody({
40
45
  >
41
46
  {title}
42
47
  </div>
43
- {icon && <div className="flex items-center">{icon}</div>}
48
+ {tooltipText ? (
49
+ <Tooltip text={tooltipText} position={tooltipPosition}>
50
+ {icon}
51
+ </Tooltip>
52
+ ) : (
53
+ <div className="flex items-center">{icon}</div>
54
+ )}
44
55
  {clickableAction && (
45
56
  <div onClick={handleActionCardBodyClick} className="flex items-center">
46
57
  {clickableAction}
@@ -15,6 +15,7 @@ export const IMAGE_VARIANT_CLASSES: { [key in ImageShape]: string } = {
15
15
  export const IMAGE_BG_CLASSES: { [key in ImageType]: string } = {
16
16
  [ImageType.Placeholder]: '',
17
17
  [ImageType.BgSolid]: 'bg-neutral-96 dark:bg-neutral-10',
18
+ [ImageType.BgWhite]: 'bg-white dark:bg-neutral-6',
18
19
  [ImageType.BgTransparent]: '',
19
20
  };
20
21
 
@@ -10,6 +10,7 @@ export enum CardType {
10
10
  export enum ImageType {
11
11
  Placeholder = 'placeholder',
12
12
  BgSolid = 'bg-solid',
13
+ BgWhite = 'bg-white',
13
14
  BgTransparent = 'bg-transparent',
14
15
  }
15
16
 
@@ -54,7 +54,7 @@ export interface SearchProps {
54
54
  /**
55
55
  * Render suggestion.
56
56
  */
57
- renderSuggestion: (suggestion: Suggestion, index: number) => React.ReactNode;
57
+ renderSuggestion?: (suggestion: Suggestion, index: number) => React.ReactNode;
58
58
  }
59
59
 
60
60
  export function Search({
@@ -157,6 +157,9 @@ export function Search({
157
157
  }
158
158
  }
159
159
  }
160
+ if (onKeyDown) {
161
+ onKeyDown(event);
162
+ }
160
163
  };
161
164
 
162
165
  return (
@@ -183,7 +186,7 @@ export function Search({
183
186
  />
184
187
  <SearchIcon />
185
188
  </div>
186
- {showSuggestions && (
189
+ {showSuggestions && renderSuggestion && (
187
190
  <div
188
191
  ref={suggestionsListRef}
189
192
  className={cx(
@@ -36,6 +36,10 @@ interface TitleProps {
36
36
  * The size of the component
37
37
  */
38
38
  size?: TitleSize;
39
+ /**
40
+ * The 'data-testid' attribute value (used in e2e tests)
41
+ */
42
+ testId?: string;
39
43
  }
40
44
 
41
45
  export function Title({
@@ -46,9 +50,13 @@ export function Title({
46
50
  supportingElement,
47
51
  tooltipPosition,
48
52
  size = TitleSize.Medium,
53
+ testId,
49
54
  }: TitleProps) {
50
55
  return (
51
- <div className={cx('flex flex-row items-center justify-between', TITLE_PADDINGS[size])}>
56
+ <div
57
+ className={cx('flex flex-row items-center justify-between', TITLE_PADDINGS[size])}
58
+ data-testid={testId}
59
+ >
52
60
  <div className="flex flex-row items-center gap-x-xxxs">
53
61
  <div className="flex flex-col justify-start">
54
62
  <div className="flex flex-row items-center gap-x-0.5 text-neutral-10 dark:text-neutral-92">
@@ -75,6 +75,11 @@ export type TableProps = {
75
75
  * The page size selector component.
76
76
  */
77
77
  pageSizeSelector?: ReactNode;
78
+
79
+ /**
80
+ * If the table should take the full height of the parent.
81
+ */
82
+ heightFull?: boolean;
78
83
  };
79
84
 
80
85
  export function Table({
@@ -85,12 +90,23 @@ export function Table({
85
90
  rowIndexes,
86
91
  children,
87
92
  pageSizeSelector,
93
+ heightFull = false,
88
94
  }: PropsWithChildren<TableProps>): JSX.Element {
89
95
  return (
90
96
  <TableProvider selectedRowIndexes={selectedRowIndexes} rowIndexes={rowIndexes}>
91
- <div className="w-full">
92
- <div className="overflow-auto">
93
- <table className="w-full table-auto">{children}</table>
97
+ <div className={cx('w-full', { 'h-full': heightFull })}>
98
+ <div
99
+ className={cx('overflow-auto', {
100
+ 'h-full': heightFull,
101
+ })}
102
+ >
103
+ <table
104
+ className={cx('w-full table-auto', {
105
+ 'h-full': heightFull,
106
+ })}
107
+ >
108
+ {children}
109
+ </table>
94
110
  </div>
95
111
  <div
96
112
  className={cx('flex w-full items-center gap-2 pt-md', {
@@ -76,6 +76,7 @@ export const IOTA_PRIMITIVES_COLOR_PALETTE = {
76
76
  warning: {
77
77
  '10': '#422C00',
78
78
  '20': '#2D1E00',
79
+ '60': '#BE8F32',
79
80
  '90': '#FFE3AB',
80
81
  },
81
82
  };
@@ -0,0 +1,59 @@
1
+ // Copyright (c) 2024 IOTA Stiftung
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import type { Meta, StoryObj } from '@storybook/react';
5
+ import { Toggle, ToggleLabelPosition, ToggleSize } from '@/components';
6
+ import { useState } from 'react';
7
+
8
+ const meta: Meta<typeof Toggle> = {
9
+ component: Toggle,
10
+ tags: ['autodocs'],
11
+ argTypes: {
12
+ isToggled: {
13
+ control: { type: 'boolean' },
14
+ },
15
+ label: {
16
+ control: { type: 'text' },
17
+ },
18
+ labelPosition: {
19
+ control: { type: 'select' },
20
+ options: Object.values(ToggleLabelPosition),
21
+ },
22
+ isDisabled: {
23
+ control: { type: 'boolean' },
24
+ },
25
+ size: {
26
+ control: { type: 'select' },
27
+ options: Object.values(ToggleSize),
28
+ },
29
+ onChange: {
30
+ action: 'changed',
31
+ },
32
+ },
33
+ args: {
34
+ isToggled: false,
35
+ isDisabled: false,
36
+ size: ToggleSize.Default,
37
+ labelPosition: ToggleLabelPosition.Right,
38
+ },
39
+ } satisfies Meta<typeof Toggle>;
40
+
41
+ export default meta;
42
+
43
+ type Story = StoryObj<typeof meta>;
44
+
45
+ export const Default: Story = {
46
+ args: {
47
+ label: 'Label',
48
+ },
49
+ render: (args) => {
50
+ const [isToggled, setIsToggled] = useState(args.isToggled);
51
+
52
+ const handleToggleChange = (newState: boolean) => {
53
+ console.log('Toggle state changed:', newState);
54
+ setIsToggled(newState);
55
+ };
56
+
57
+ return <Toggle {...args} isToggled={isToggled} onChange={handleToggleChange} />;
58
+ },
59
+ };