@iota/apps-ui-kit 0.2.1 → 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/CHANGELOG.md +51 -0
- package/README.md +16 -0
- package/dist/index.d.ts +91 -15
- package/dist/index.es.js +4536 -4297
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +52 -52
- package/dist/index.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +3 -3
- package/src/lib/components/atoms/badge/badge.classes.ts +9 -0
- package/src/lib/components/atoms/badge/badge.enums.ts +3 -0
- package/src/lib/components/atoms/button/button.classes.ts +4 -4
- package/src/lib/components/atoms/index.ts +1 -0
- package/src/lib/components/atoms/info-box/InfoBox.tsx +1 -1
- package/src/lib/components/atoms/info-box/infoBox.classes.ts +7 -2
- package/src/lib/components/atoms/info-box/infoBox.enums.ts +2 -0
- package/src/lib/components/atoms/snackbar/snackbar.classes.ts +5 -1
- package/src/lib/components/atoms/snackbar/snackbar.enums.tsx +2 -0
- package/src/lib/components/atoms/toggle/Toggle.tsx +140 -0
- package/src/lib/components/atoms/toggle/index.ts +6 -0
- package/src/lib/components/atoms/toggle/toggle.classes.ts +41 -0
- package/src/lib/components/atoms/toggle/toggle.enums.ts +12 -0
- package/src/lib/components/atoms/tooltip/Tooltip.tsx +4 -1
- package/src/lib/components/atoms/visual-asset-card/VisualAssetCard.tsx +26 -18
- package/src/lib/components/molecules/address/Address.tsx +1 -1
- package/src/lib/components/molecules/card/CardAction.tsx +4 -2
- package/src/lib/components/molecules/card/CardBody.tsx +12 -1
- package/src/lib/components/molecules/card/card.classes.ts +1 -0
- package/src/lib/components/molecules/card/card.enums.ts +1 -0
- package/src/lib/components/molecules/input/input.classes.ts +2 -2
- package/src/lib/components/molecules/search/Search.tsx +39 -5
- package/src/lib/components/molecules/table-header-cell/TableHeaderCell.tsx +27 -13
- package/src/lib/components/molecules/title/Title.tsx +9 -1
- package/src/lib/components/organisms/accordion/Accordion.tsx +3 -5
- package/src/lib/components/organisms/dialog/Dialog.tsx +6 -5
- package/src/lib/components/organisms/navbar/Navbar.tsx +10 -10
- package/src/lib/components/organisms/table/Table.tsx +19 -3
- package/src/lib/constants/colors.constants.ts +6 -0
- package/src/lib/styles/index.css +13 -0
- package/src/lib/tailwind/base.presets.ts +2 -0
- package/src/lib/tailwind/plugins.ts +8 -0
- package/src/storybook/stories/atoms/Toggle.stories.tsx +59 -0
- 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.
|
|
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.
|
|
33
|
+
"@iota/apps-ui-icons": "0.3.0"
|
|
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.
|
|
57
|
+
"vite": "^5.4.12",
|
|
58
58
|
"vite-plugin-dts": "^4.3.0",
|
|
59
59
|
"vite-tsconfig-paths": "^4.2.0"
|
|
60
60
|
},
|
|
@@ -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';
|
|
@@ -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-
|
|
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-
|
|
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
|
};
|
|
@@ -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-
|
|
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
|
};
|
|
@@ -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,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';
|
|
@@ -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
|
|
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
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
<
|
|
85
|
-
|
|
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
|
|
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
|
-
|
|
31
|
-
|
|
30
|
+
if (onClick) {
|
|
31
|
+
event.stopPropagation();
|
|
32
|
+
onClick();
|
|
33
|
+
}
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
if (type === CardActionType.Link) {
|
|
@@ -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
|
-
{
|
|
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
|
|
|
@@ -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
|
-
'
|
|
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
|
|
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 =
|
|
@@ -54,7 +54,7 @@ export interface SearchProps {
|
|
|
54
54
|
/**
|
|
55
55
|
* Render suggestion.
|
|
56
56
|
*/
|
|
57
|
-
renderSuggestion
|
|
57
|
+
renderSuggestion?: (suggestion: Suggestion, index: number) => React.ReactNode;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
export function Search({
|
|
@@ -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,36 @@ 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
|
+
if (onKeyDown) {
|
|
161
|
+
onKeyDown(event);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
137
165
|
return (
|
|
138
166
|
<div className="relative w-full">
|
|
139
167
|
<div
|
|
@@ -148,7 +176,7 @@ export function Search({
|
|
|
148
176
|
type="text"
|
|
149
177
|
value={searchValue}
|
|
150
178
|
onChange={handleChange}
|
|
151
|
-
onKeyDown={
|
|
179
|
+
onKeyDown={handleKeyDown}
|
|
152
180
|
onFocus={() => setIsSuggestionsVisible(true)}
|
|
153
181
|
placeholder={placeholder}
|
|
154
182
|
className={cx(
|
|
@@ -158,7 +186,7 @@ export function Search({
|
|
|
158
186
|
/>
|
|
159
187
|
<SearchIcon />
|
|
160
188
|
</div>
|
|
161
|
-
{showSuggestions && (
|
|
189
|
+
{showSuggestions && renderSuggestion && (
|
|
162
190
|
<div
|
|
163
191
|
ref={suggestionsListRef}
|
|
164
192
|
className={cx(
|
|
@@ -168,15 +196,21 @@ export function Search({
|
|
|
168
196
|
>
|
|
169
197
|
<Divider width="w-11/12" />
|
|
170
198
|
{isLoading ? (
|
|
171
|
-
<div className="
|
|
199
|
+
<div className="px-md py-sm">
|
|
172
200
|
<Loader className="animate-spin" />
|
|
173
201
|
</div>
|
|
174
202
|
) : (
|
|
175
203
|
suggestions.map((suggestion, index) => (
|
|
176
204
|
<div
|
|
177
|
-
className="w-full"
|
|
178
205
|
key={suggestion.id}
|
|
179
206
|
onClick={() => handleOnSuggestionClick(suggestion)}
|
|
207
|
+
onMouseEnter={() => setSelectedIndex(index)}
|
|
208
|
+
className={cx(
|
|
209
|
+
'w-full cursor-pointer px-md py-sm',
|
|
210
|
+
selectedIndex === index
|
|
211
|
+
? 'bg-shader-primary-dark-16 dark:bg-shader-inverted-dark-16'
|
|
212
|
+
: '',
|
|
213
|
+
)}
|
|
180
214
|
>
|
|
181
215
|
{renderSuggestion(suggestion, index)}
|
|
182
216
|
</div>
|