@iota/apps-ui-kit 0.11.0 → 0.13.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 +27 -0
- package/dist/index.d.ts +43 -14
- package/dist/index.es.js +1281 -1214
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +15 -15
- package/dist/index.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +2 -8
- package/src/lib/components/atoms/badge/Badge.tsx +18 -2
- package/src/lib/components/atoms/button/Button.tsx +1 -1
- package/src/lib/components/atoms/checkbox/Checkbox.tsx +2 -2
- package/src/lib/components/atoms/divider/Divider.tsx +2 -3
- package/src/lib/components/atoms/info-box/InfoBox.tsx +1 -1
- package/src/lib/components/atoms/key-value-info/KeyValueInfo.tsx +20 -5
- package/src/lib/components/atoms/tooltip/Tooltip.tsx +89 -21
- package/src/lib/components/molecules/card/CardAction.tsx +2 -2
- package/src/lib/components/molecules/card/CardBody.tsx +1 -1
- package/src/lib/components/molecules/display-stats/DisplayStats.tsx +2 -2
- package/src/lib/components/molecules/input/Input.tsx +2 -2
- package/src/lib/components/molecules/input/InputWrapper.tsx +3 -2
- package/src/lib/components/molecules/select/Select.tsx +1 -2
- package/src/lib/components/molecules/table-cell/TableCell.tsx +6 -0
- package/src/lib/components/organisms/accordion/Accordion.tsx +10 -1
- package/src/lib/components/organisms/table/Table.tsx +31 -3
- package/src/lib/constants/index.ts +1 -0
- package/src/lib/constants/interactive.constants.ts +19 -0
- package/src/lib/styles/base.css +3 -2
- package/src/lib/tailwind/base.presets.ts +2 -2
- package/src/lib/tailwind/plugins.ts +14 -0
- package/src/storybook/stories/atoms/Panel.stories.tsx +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iota/apps-ui-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"repository": "https://github.com/iotaledger/ts-packages",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"classnames": "^2.5.1",
|
|
28
28
|
"lodash.merge": "^4.6.2",
|
|
29
29
|
"react-number-format": "^5.2.2",
|
|
30
|
-
"@iota/apps-ui-icons": "0.7.
|
|
30
|
+
"@iota/apps-ui-icons": "0.7.6"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"react": "^18 || ^19",
|
|
@@ -58,12 +58,6 @@
|
|
|
58
58
|
"vite-plugin-dts": "^4.5.4"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
|
-
"prettier:check": "prettier -c --ignore-unknown --ignore-path=../../.prettierignore --ignore-path=.prettierignore .",
|
|
62
|
-
"prettier:fix": "prettier -w --ignore-unknown --ignore-path=../../.prettierignore --ignore-path=.prettierignore .",
|
|
63
|
-
"eslint:check": "eslint --max-warnings=0 .",
|
|
64
|
-
"eslint:fix": "pnpm run eslint:check --fix",
|
|
65
|
-
"lint": "pnpm run eslint:check && pnpm run prettier:check",
|
|
66
|
-
"lint:fix": "pnpm run eslint:fix && pnpm run prettier:fix",
|
|
67
61
|
"storybook": "storybook dev -p 6006",
|
|
68
62
|
"build-storybook": "storybook build",
|
|
69
63
|
"build-package": "tsc --project tsconfig.package.json && vite build",
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
4
|
import cx from 'classnames';
|
|
5
|
+
import type { ReactNode } from 'react';
|
|
5
6
|
import { BadgeSize, BadgeType } from './badge.enums';
|
|
6
7
|
import { BACKGROUND_COLORS, BORDER_COLORS, TEXT_COLORS } from './badge.classes';
|
|
7
8
|
|
|
@@ -18,9 +19,18 @@ interface BadgeProps {
|
|
|
18
19
|
* The size of the badge
|
|
19
20
|
*/
|
|
20
21
|
size?: BadgeSize;
|
|
22
|
+
/**
|
|
23
|
+
* An icon rendered before the label.
|
|
24
|
+
*/
|
|
25
|
+
icon?: ReactNode;
|
|
21
26
|
}
|
|
22
27
|
|
|
23
|
-
export function Badge({
|
|
28
|
+
export function Badge({
|
|
29
|
+
type,
|
|
30
|
+
label,
|
|
31
|
+
size = BadgeSize.Medium,
|
|
32
|
+
icon,
|
|
33
|
+
}: BadgeProps): React.JSX.Element {
|
|
24
34
|
const backgroundClasses = BACKGROUND_COLORS[type];
|
|
25
35
|
const textClasses = TEXT_COLORS[type];
|
|
26
36
|
const isSmall = size === BadgeSize.Small;
|
|
@@ -31,7 +41,8 @@ export function Badge({ type, label, size = BadgeSize.Medium }: BadgeProps): Rea
|
|
|
31
41
|
return (
|
|
32
42
|
<div
|
|
33
43
|
className={cx(
|
|
34
|
-
'inline-flex items-center
|
|
44
|
+
'inline-flex items-center rounded-full disabled:opacity-30',
|
|
45
|
+
icon ? 'gap-xxs' : 'space-x-2',
|
|
35
46
|
{ border: !isSmall },
|
|
36
47
|
{ [BORDER_COLORS[type]]: !isSmall },
|
|
37
48
|
{ 'badge-ring-outlined': isSmall && isOutlined },
|
|
@@ -39,6 +50,11 @@ export function Badge({ type, label, size = BadgeSize.Medium }: BadgeProps): Rea
|
|
|
39
50
|
labelClasses,
|
|
40
51
|
)}
|
|
41
52
|
>
|
|
53
|
+
{icon && (
|
|
54
|
+
<span className={cx('flex h-4 w-4 items-center justify-center', textClasses)}>
|
|
55
|
+
{icon}
|
|
56
|
+
</span>
|
|
57
|
+
)}
|
|
42
58
|
<span className={cx(textSizeClass, textClasses)}>{label}</span>
|
|
43
59
|
</div>
|
|
44
60
|
);
|
|
@@ -98,7 +98,7 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
|
|
98
98
|
<span
|
|
99
99
|
onClick={() => inputRef.current?.click()}
|
|
100
100
|
className={cx(
|
|
101
|
-
'checkbox-base checkbox-state checkbox-icon checkbox-icon-hidden',
|
|
101
|
+
'checkbox-base checkbox-state checkbox-icon checkbox-icon-hidden shrink-0',
|
|
102
102
|
'checkbox-border-default',
|
|
103
103
|
'peer-[&:is(:checked,:indeterminate)]:checkbox-border-checked',
|
|
104
104
|
'peer-[&:is(:checked,:indeterminate)]:checkbox-bg-checked',
|
|
@@ -119,7 +119,7 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
|
|
119
119
|
|
|
120
120
|
function LabelText({ label, name }: Pick<CheckboxProps, 'label' | 'name'>) {
|
|
121
121
|
return (
|
|
122
|
-
<label htmlFor={name} className="checkbox-label checkbox-label-disabled">
|
|
122
|
+
<label htmlFor={name} className="checkbox-label checkbox-label-disabled flex-1">
|
|
123
123
|
{label}
|
|
124
124
|
</label>
|
|
125
125
|
);
|
|
@@ -33,9 +33,8 @@ export function Divider({
|
|
|
33
33
|
lineHeight = DEFAULT_LINE_HEIGHT,
|
|
34
34
|
}: DividerProps): React.JSX.Element {
|
|
35
35
|
// Set height and width of divider line based on type
|
|
36
|
-
const lineStyle =
|
|
37
|
-
|
|
38
|
-
};
|
|
36
|
+
const lineStyle =
|
|
37
|
+
type === DividerType.Horizontal ? { height: lineHeight } : { width: lineHeight };
|
|
39
38
|
|
|
40
39
|
let dividerSize = DIVIDER_FULL_WIDTH[type];
|
|
41
40
|
if (width && type === DividerType.Horizontal) {
|
|
@@ -59,7 +59,7 @@ export function InfoBox({
|
|
|
59
59
|
{icon}
|
|
60
60
|
</span>
|
|
61
61
|
)}
|
|
62
|
-
<div className="flex flex-col gap-1">
|
|
62
|
+
<div className="flex min-w-0 flex-col gap-1 [overflow-wrap:anywhere]">
|
|
63
63
|
{title && <span className="infobox-text-title text-title-sm">{title}</span>}
|
|
64
64
|
{supportingText && (
|
|
65
65
|
<span className="infobox-supporting-text text-body-sm">{supportingText}</span>
|
|
@@ -13,7 +13,7 @@ interface KeyValueProps {
|
|
|
13
13
|
/**
|
|
14
14
|
* The key of the KeyValue.
|
|
15
15
|
*/
|
|
16
|
-
keyText:
|
|
16
|
+
keyText: ReactNode;
|
|
17
17
|
/**
|
|
18
18
|
* An icon shown next to the key text (optional).
|
|
19
19
|
*/
|
|
@@ -62,6 +62,11 @@ interface KeyValueProps {
|
|
|
62
62
|
* Reverse the KeyValue (optional).
|
|
63
63
|
*/
|
|
64
64
|
isReverse?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Use a receipt-style row with a dotted leader and right-aligned value.
|
|
67
|
+
* This is opt-in so existing KeyValueInfo layouts remain unchanged.
|
|
68
|
+
*/
|
|
69
|
+
layout?: 'default' | 'receipt';
|
|
65
70
|
/**
|
|
66
71
|
* Text shown on value hover.
|
|
67
72
|
*/
|
|
@@ -82,9 +87,11 @@ export function KeyValueInfo({
|
|
|
82
87
|
onCopyError,
|
|
83
88
|
fullwidth,
|
|
84
89
|
isReverse = false,
|
|
90
|
+
layout = 'default',
|
|
85
91
|
valueHoverTitle,
|
|
86
92
|
}: KeyValueProps): React.JSX.Element {
|
|
87
93
|
const flexDirectionClass = isReverse ? 'flex-row-reverse' : 'flex-row';
|
|
94
|
+
const isReceiptLayout = layout === 'receipt';
|
|
88
95
|
async function handleCopyClick(event: React.MouseEvent<HTMLButtonElement>) {
|
|
89
96
|
if (!navigator.clipboard) {
|
|
90
97
|
return;
|
|
@@ -112,12 +119,12 @@ export function KeyValueInfo({
|
|
|
112
119
|
)}
|
|
113
120
|
>
|
|
114
121
|
<div
|
|
115
|
-
className={cx('flex shrink-0 flex-row items-center gap-x-0.5', {
|
|
116
|
-
'w-1/4': !fullwidth,
|
|
122
|
+
className={cx('flex max-w-full shrink-0 flex-row items-center gap-x-0.5', {
|
|
123
|
+
'w-1/4': !fullwidth && !isReceiptLayout,
|
|
117
124
|
})}
|
|
118
125
|
>
|
|
119
126
|
{keyIcon}
|
|
120
|
-
<span className="key-value-key-text-color break-
|
|
127
|
+
<span className="key-value-key-text-color min-w-0 break-words text-body-md">
|
|
121
128
|
{keyText}
|
|
122
129
|
</span>
|
|
123
130
|
{tooltipText && (
|
|
@@ -126,10 +133,18 @@ export function KeyValueInfo({
|
|
|
126
133
|
</Tooltip>
|
|
127
134
|
)}
|
|
128
135
|
</div>
|
|
136
|
+
{isReceiptLayout && !fullwidth && (
|
|
137
|
+
<span
|
|
138
|
+
aria-hidden="true"
|
|
139
|
+
className="mb-1 min-w-[0.5rem] flex-1 border-b border-dotted border-iota-neutral-80 opacity-60 dark:border-iota-neutral-20"
|
|
140
|
+
/>
|
|
141
|
+
)}
|
|
129
142
|
<div
|
|
130
143
|
className={cx('flex min-w-0 flex-row items-baseline gap-1 break-all', {
|
|
131
|
-
'w-3/4': !fullwidth,
|
|
144
|
+
'w-3/4': !fullwidth && !isReceiptLayout,
|
|
145
|
+
'max-w-[60%] justify-end text-right': !fullwidth && isReceiptLayout,
|
|
132
146
|
'flex-wrap': fullwidth,
|
|
147
|
+
'justify-end text-right': fullwidth && isReceiptLayout,
|
|
133
148
|
truncate: isTruncated,
|
|
134
149
|
})}
|
|
135
150
|
>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
4
|
import { useRef, useState, useLayoutEffect } from 'react';
|
|
5
|
-
import type { PropsWithChildren, ReactNode } from 'react';
|
|
5
|
+
import type { MouseEvent, PropsWithChildren, ReactNode } from 'react';
|
|
6
6
|
import { createPortal } from 'react-dom';
|
|
7
7
|
import cx from 'classnames';
|
|
8
8
|
import { TooltipPosition } from './tooltip.enums';
|
|
@@ -16,6 +16,13 @@ interface TooltipProps {
|
|
|
16
16
|
closeDelay?: number;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
const OPPOSITE_POSITION: Record<TooltipPosition, TooltipPosition> = {
|
|
20
|
+
[TooltipPosition.Top]: TooltipPosition.Bottom,
|
|
21
|
+
[TooltipPosition.Bottom]: TooltipPosition.Top,
|
|
22
|
+
[TooltipPosition.Left]: TooltipPosition.Right,
|
|
23
|
+
[TooltipPosition.Right]: TooltipPosition.Left,
|
|
24
|
+
};
|
|
25
|
+
|
|
19
26
|
export function Tooltip({
|
|
20
27
|
text,
|
|
21
28
|
position = TooltipPosition.Top,
|
|
@@ -29,7 +36,7 @@ export function Tooltip({
|
|
|
29
36
|
const tooltipRef = useRef<HTMLDivElement>(null);
|
|
30
37
|
|
|
31
38
|
const [visible, setVisible] = useState(false);
|
|
32
|
-
const [coords, setCoords] = useState
|
|
39
|
+
const [coords, setCoords] = useState({ top: 0, left: 0, overlapsTrigger: false });
|
|
33
40
|
|
|
34
41
|
const openTimer = useRef<ReturnType<typeof setTimeout>>();
|
|
35
42
|
const closeTimer = useRef<ReturnType<typeof setTimeout>>();
|
|
@@ -49,54 +56,110 @@ export function Tooltip({
|
|
|
49
56
|
closeTimer.current = setTimeout(() => setVisible(false), closeDelay);
|
|
50
57
|
};
|
|
51
58
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const rect = triggerRef.current?.getBoundingClientRect();
|
|
56
|
-
if (!rect) return;
|
|
57
|
-
|
|
58
|
-
const pos = {
|
|
59
|
+
const computePosition = (rect: DOMRect, placement: TooltipPosition) =>
|
|
60
|
+
({
|
|
59
61
|
[TooltipPosition.Top]: {
|
|
60
62
|
top: rect.top - offset,
|
|
61
63
|
left: rect.left + rect.width / 2,
|
|
62
64
|
transform: 'translate(-50%, -100%)',
|
|
65
|
+
anchorX: 0.5,
|
|
66
|
+
anchorY: 1,
|
|
63
67
|
},
|
|
64
68
|
[TooltipPosition.Bottom]: {
|
|
65
69
|
top: rect.bottom + offset,
|
|
66
70
|
left: rect.left + rect.width / 2,
|
|
67
71
|
transform: 'translate(-50%, 0)',
|
|
72
|
+
anchorX: 0.5,
|
|
73
|
+
anchorY: 0,
|
|
68
74
|
},
|
|
69
75
|
[TooltipPosition.Left]: {
|
|
70
76
|
top: rect.top + rect.height / 2,
|
|
71
77
|
left: rect.left - offset,
|
|
72
78
|
transform: 'translate(-100%, -50%)',
|
|
79
|
+
anchorX: 1,
|
|
80
|
+
anchorY: 0.5,
|
|
73
81
|
},
|
|
74
82
|
[TooltipPosition.Right]: {
|
|
75
83
|
top: rect.top + rect.height / 2,
|
|
76
84
|
left: rect.right + offset,
|
|
77
85
|
transform: 'translate(0, -50%)',
|
|
86
|
+
anchorX: 0,
|
|
87
|
+
anchorY: 0.5,
|
|
78
88
|
},
|
|
79
|
-
}[
|
|
89
|
+
})[placement];
|
|
90
|
+
|
|
91
|
+
const clampDelta = (boxStart: number, size: number, viewportSize: number, margin: number) => {
|
|
92
|
+
const boxEnd = boxStart + size;
|
|
93
|
+
if (boxEnd > viewportSize - margin) return viewportSize - margin - boxEnd;
|
|
94
|
+
if (boxStart < margin) return margin - boxStart;
|
|
95
|
+
return 0;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
useLayoutEffect(() => {
|
|
99
|
+
if (!visible) return;
|
|
80
100
|
|
|
81
|
-
|
|
82
|
-
|
|
101
|
+
const rect = triggerRef.current?.getBoundingClientRect();
|
|
102
|
+
const tooltipEl = tooltipRef.current;
|
|
103
|
+
if (!rect || !tooltipEl) return;
|
|
104
|
+
|
|
105
|
+
const { width, height } = tooltipEl.getBoundingClientRect();
|
|
106
|
+
const margin = 8;
|
|
107
|
+
const isVertical = position === TooltipPosition.Top || position === TooltipPosition.Bottom;
|
|
108
|
+
|
|
109
|
+
const fitsMainAxis = (pos: ReturnType<typeof computePosition>) =>
|
|
110
|
+
isVertical
|
|
111
|
+
? clampDelta(pos.top - height * pos.anchorY, height, window.innerHeight, margin) ===
|
|
112
|
+
0
|
|
113
|
+
: clampDelta(pos.left - width * pos.anchorX, width, window.innerWidth, margin) ===
|
|
114
|
+
0;
|
|
115
|
+
|
|
116
|
+
let pos = computePosition(rect, position);
|
|
117
|
+
if (!fitsMainAxis(pos)) {
|
|
118
|
+
const flipped = computePosition(rect, OPPOSITE_POSITION[position]);
|
|
119
|
+
if (fitsMainAxis(flipped)) pos = flipped;
|
|
120
|
+
}
|
|
121
|
+
tooltipEl.style.transform = pos.transform;
|
|
122
|
+
|
|
123
|
+
const boxLeft = pos.left - width * pos.anchorX;
|
|
124
|
+
const boxTop = pos.top - height * pos.anchorY;
|
|
125
|
+
|
|
126
|
+
const deltaX = clampDelta(boxLeft, width, window.innerWidth, margin);
|
|
127
|
+
const deltaY = clampDelta(boxTop, height, window.innerHeight, margin);
|
|
128
|
+
|
|
129
|
+
const left = boxLeft + deltaX;
|
|
130
|
+
const top = boxTop + deltaY;
|
|
131
|
+
const overlapsTrigger =
|
|
132
|
+
left < rect.right &&
|
|
133
|
+
left + width > rect.left &&
|
|
134
|
+
top < rect.bottom &&
|
|
135
|
+
top + height > rect.top;
|
|
136
|
+
|
|
137
|
+
setCoords({ top: pos.top + deltaY, left: pos.left + deltaX, overlapsTrigger });
|
|
83
138
|
}, [visible, position, offset]);
|
|
84
139
|
|
|
85
140
|
useLayoutEffect(() => {
|
|
86
141
|
if (!visible) return;
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
setCoords((prev) => ({ ...prev, left: rect.left + rect.width / 2 }));
|
|
142
|
+
const dismiss = () => {
|
|
143
|
+
clearTimers();
|
|
144
|
+
setVisible(false);
|
|
91
145
|
};
|
|
92
|
-
window.addEventListener('scroll',
|
|
93
|
-
window.addEventListener('resize',
|
|
146
|
+
window.addEventListener('scroll', dismiss, true);
|
|
147
|
+
window.addEventListener('resize', dismiss);
|
|
94
148
|
return () => {
|
|
95
|
-
window.removeEventListener('scroll',
|
|
96
|
-
window.removeEventListener('resize',
|
|
149
|
+
window.removeEventListener('scroll', dismiss, true);
|
|
150
|
+
window.removeEventListener('resize', dismiss);
|
|
97
151
|
};
|
|
98
152
|
}, [visible]);
|
|
99
153
|
|
|
154
|
+
// Keeps a link or button wrapping the trigger from acting on clicks meant to open the tooltip.
|
|
155
|
+
const handleTriggerClick = (event: MouseEvent<HTMLDivElement>) => {
|
|
156
|
+
const interactive = (event.target as Element).closest('a, button');
|
|
157
|
+
if (!interactive || triggerRef.current?.contains(interactive)) return;
|
|
158
|
+
event.preventDefault();
|
|
159
|
+
event.stopPropagation();
|
|
160
|
+
open();
|
|
161
|
+
};
|
|
162
|
+
|
|
100
163
|
// z-[9999999999]: needed because we must exceed the popup’s ≈2 147 483 647 z-index;
|
|
101
164
|
// otherwise the tooltip renders but stays invisible inside a Chrome extension
|
|
102
165
|
const base = 'z-[9999999999] w-max rounded p-xs tooltip-bg tooltip-text-color';
|
|
@@ -110,6 +173,7 @@ export function Tooltip({
|
|
|
110
173
|
onFocus={open}
|
|
111
174
|
onMouseLeave={close}
|
|
112
175
|
onBlur={close}
|
|
176
|
+
onClick={handleTriggerClick}
|
|
113
177
|
>
|
|
114
178
|
{children}
|
|
115
179
|
</div>
|
|
@@ -126,7 +190,11 @@ export function Tooltip({
|
|
|
126
190
|
transition: 'opacity .15s ease',
|
|
127
191
|
opacity: 1,
|
|
128
192
|
}}
|
|
129
|
-
className={cx(
|
|
193
|
+
className={cx(
|
|
194
|
+
base,
|
|
195
|
+
maxWidth,
|
|
196
|
+
coords.overlapsTrigger && 'pointer-events-none',
|
|
197
|
+
)}
|
|
130
198
|
onMouseEnter={open}
|
|
131
199
|
onMouseLeave={close}
|
|
132
200
|
>
|
|
@@ -6,7 +6,7 @@ import { CardActionType } from './card.enums';
|
|
|
6
6
|
import { Button, ButtonSize, ButtonType } from '@/components/atoms/button';
|
|
7
7
|
|
|
8
8
|
export type CardActionProps = {
|
|
9
|
-
title?:
|
|
9
|
+
title?: React.ReactNode;
|
|
10
10
|
subtitle?: string | React.ReactNode;
|
|
11
11
|
type: CardActionType;
|
|
12
12
|
onClick?: () => void;
|
|
@@ -51,7 +51,7 @@ export function CardAction({
|
|
|
51
51
|
<div className="card-action-title-color font-inter text-label-md">{title}</div>
|
|
52
52
|
)}
|
|
53
53
|
{subtitle && (
|
|
54
|
-
<div className="card-action-subtitle-color font-inter text-label-sm">
|
|
54
|
+
<div className="card-action-subtitle-color flex flex-row justify-end font-inter text-label-sm">
|
|
55
55
|
{subtitle}
|
|
56
56
|
</div>
|
|
57
57
|
)}
|
|
@@ -6,7 +6,7 @@ import { Tooltip } from '@/components/atoms/tooltip';
|
|
|
6
6
|
import type { TooltipPosition } from '@/components/atoms/tooltip';
|
|
7
7
|
|
|
8
8
|
export type CardBodyProps = {
|
|
9
|
-
title:
|
|
9
|
+
title: ReactNode;
|
|
10
10
|
subtitle?: string | ReactNode;
|
|
11
11
|
clickableAction?: React.ReactNode;
|
|
12
12
|
icon?: React.ReactNode;
|
|
@@ -28,7 +28,7 @@ interface DisplayStatsProps {
|
|
|
28
28
|
/**
|
|
29
29
|
* The tooltip text.
|
|
30
30
|
*/
|
|
31
|
-
tooltipText?:
|
|
31
|
+
tooltipText?: ReactNode;
|
|
32
32
|
/**
|
|
33
33
|
* The value of the stats.
|
|
34
34
|
*/
|
|
@@ -123,7 +123,7 @@ export function DisplayStats({
|
|
|
123
123
|
{icon && <span className="display-stats-icon-color">{icon}</span>}
|
|
124
124
|
</div>
|
|
125
125
|
<div className="flex w-full flex-row items-baseline gap-xxs">
|
|
126
|
-
<span className={cx('break-all', valueClass)}>{value}</span>
|
|
126
|
+
<span className={cx('min-w-0 break-all', valueClass)}>{value}</span>
|
|
127
127
|
{supportingLabel && (
|
|
128
128
|
<span className={cx('opacity-40', supportingLabelTextClass)}>
|
|
129
129
|
{supportingLabel}
|
|
@@ -31,7 +31,7 @@ export interface BaseInputProps extends InputWrapperProps {
|
|
|
31
31
|
/**
|
|
32
32
|
* Amount counter that is shown at the side of the caption text.
|
|
33
33
|
*/
|
|
34
|
-
amountCounter?:
|
|
34
|
+
amountCounter?: React.ReactNode;
|
|
35
35
|
/**
|
|
36
36
|
* Trailing element that is shown after the input
|
|
37
37
|
*/
|
|
@@ -47,7 +47,7 @@ export interface BaseInputProps extends InputWrapperProps {
|
|
|
47
47
|
/**
|
|
48
48
|
* Text that is shown below the value of the input.
|
|
49
49
|
*/
|
|
50
|
-
supportingValue?:
|
|
50
|
+
supportingValue?: React.ReactNode;
|
|
51
51
|
/**
|
|
52
52
|
* Default value of the input
|
|
53
53
|
*/
|
|
@@ -5,6 +5,7 @@ import cx from 'classnames';
|
|
|
5
5
|
import { SecondaryText } from '@/components/atoms/secondary-text';
|
|
6
6
|
import { LABEL_CLASSES } from './input.classes';
|
|
7
7
|
import { createElement } from 'react';
|
|
8
|
+
import type { ReactNode } from 'react';
|
|
8
9
|
|
|
9
10
|
export enum LabelHtmlTag {
|
|
10
11
|
Label = 'label',
|
|
@@ -19,7 +20,7 @@ export interface InputWrapperProps {
|
|
|
19
20
|
/**
|
|
20
21
|
* Shows a caption with the text below the input.
|
|
21
22
|
*/
|
|
22
|
-
caption?:
|
|
23
|
+
caption?: ReactNode;
|
|
23
24
|
/**
|
|
24
25
|
* Error Message. Overrides the caption.
|
|
25
26
|
*/
|
|
@@ -27,7 +28,7 @@ export interface InputWrapperProps {
|
|
|
27
28
|
/**
|
|
28
29
|
* Amount counter that is shown at the side of the caption text.
|
|
29
30
|
*/
|
|
30
|
-
amountCounter?:
|
|
31
|
+
amountCounter?: React.ReactNode;
|
|
31
32
|
/**
|
|
32
33
|
* Is the input required
|
|
33
34
|
*/
|
|
@@ -17,8 +17,7 @@ export type SelectOption =
|
|
|
17
17
|
| { id: string; label: React.ReactNode };
|
|
18
18
|
|
|
19
19
|
interface SelectProps
|
|
20
|
-
extends Pick<React.HTMLProps<HTMLSelectElement>, 'disabled'>,
|
|
21
|
-
React.AriaAttributes {
|
|
20
|
+
extends Pick<React.HTMLProps<HTMLSelectElement>, 'disabled'>, React.AriaAttributes {
|
|
22
21
|
/**
|
|
23
22
|
* The selected option value.
|
|
24
23
|
*/
|
|
@@ -17,15 +17,21 @@ interface TableCellBaseProps {
|
|
|
17
17
|
* Whether the cell content should be centered.
|
|
18
18
|
*/
|
|
19
19
|
isContentCentered?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* The number of columns the cell should span.
|
|
22
|
+
*/
|
|
23
|
+
colSpan?: number;
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
export function TableCellBase({
|
|
23
27
|
children,
|
|
24
28
|
hasLastBorderNoneClass,
|
|
25
29
|
isContentCentered,
|
|
30
|
+
colSpan,
|
|
26
31
|
}: PropsWithChildren<TableCellBaseProps>) {
|
|
27
32
|
return (
|
|
28
33
|
<td
|
|
34
|
+
colSpan={colSpan}
|
|
29
35
|
className={cx(
|
|
30
36
|
'table-cell-border-color h-14 border-b px-md',
|
|
31
37
|
{ 'last:border-none': hasLastBorderNoneClass },
|
|
@@ -30,6 +30,13 @@ export interface AccordionHeaderProps {
|
|
|
30
30
|
* Flag for hiding border.
|
|
31
31
|
*/
|
|
32
32
|
hideBorder?: boolean;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Disable the header state layer while an interactive child is active.
|
|
36
|
+
* This keeps the parent hover treatment from competing with child links
|
|
37
|
+
* and controls.
|
|
38
|
+
*/
|
|
39
|
+
disableStateLayer?: boolean;
|
|
33
40
|
}
|
|
34
41
|
|
|
35
42
|
interface AccordionContentProps {
|
|
@@ -44,6 +51,7 @@ export function AccordionHeader({
|
|
|
44
51
|
children,
|
|
45
52
|
isExpanded,
|
|
46
53
|
hideArrow,
|
|
54
|
+
disableStateLayer = false,
|
|
47
55
|
}: PropsWithChildren<AccordionHeaderProps>) {
|
|
48
56
|
return (
|
|
49
57
|
<div
|
|
@@ -51,7 +59,8 @@ export function AccordionHeader({
|
|
|
51
59
|
role="button"
|
|
52
60
|
aria-expanded={isExpanded}
|
|
53
61
|
className={cx(
|
|
54
|
-
'
|
|
62
|
+
'relative flex cursor-pointer items-center justify-between gap-md overflow-hidden rounded-xl py-sm--rs',
|
|
63
|
+
!disableStateLayer && 'state-layer',
|
|
55
64
|
{
|
|
56
65
|
'pr-md--rs': !hideArrow,
|
|
57
66
|
},
|
|
@@ -8,6 +8,7 @@ import type { ButtonProps } from '@/components/atoms';
|
|
|
8
8
|
import { Button, ButtonSize, ButtonType, Checkbox } from '@/components/atoms';
|
|
9
9
|
import { TableCellBase, TableHeaderCell } from '@/components/molecules';
|
|
10
10
|
import { ArrowLeft, DoubleArrowLeft, ArrowRight, DoubleArrowRight } from '@iota/apps-ui-icons';
|
|
11
|
+
import { INTERACTIVE_ELEMENT_SELECTOR } from '@/lib/constants';
|
|
11
12
|
|
|
12
13
|
export interface TablePaginationOptions {
|
|
13
14
|
/**
|
|
@@ -147,7 +148,7 @@ export function Table({
|
|
|
147
148
|
/>
|
|
148
149
|
</>
|
|
149
150
|
)}
|
|
150
|
-
{action
|
|
151
|
+
{action}
|
|
151
152
|
</div>
|
|
152
153
|
)}
|
|
153
154
|
{supportingLabel || pageSizeSelector ? (
|
|
@@ -174,12 +175,39 @@ export function TableHeader({ children }: PropsWithChildren): JSX.Element {
|
|
|
174
175
|
return <thead>{children}</thead>;
|
|
175
176
|
}
|
|
176
177
|
|
|
178
|
+
function isRowClickIgnored(event: React.MouseEvent<HTMLTableRowElement>): boolean {
|
|
179
|
+
const interactiveElement =
|
|
180
|
+
event.target instanceof Element && event.target.closest(INTERACTIVE_ELEMENT_SELECTOR);
|
|
181
|
+
if (interactiveElement && event.currentTarget.contains(interactiveElement)) return true;
|
|
182
|
+
return !!window.getSelection()?.toString();
|
|
183
|
+
}
|
|
184
|
+
|
|
177
185
|
export function TableRow({
|
|
178
186
|
children,
|
|
179
187
|
leading,
|
|
180
|
-
|
|
188
|
+
onClick,
|
|
189
|
+
onAuxClick,
|
|
190
|
+
}: PropsWithChildren<{
|
|
191
|
+
leading?: React.ReactNode;
|
|
192
|
+
onClick?: React.MouseEventHandler<HTMLTableRowElement>;
|
|
193
|
+
onAuxClick?: React.MouseEventHandler<HTMLTableRowElement>;
|
|
194
|
+
}>): JSX.Element {
|
|
195
|
+
function withRowClickGuard(handler?: React.MouseEventHandler<HTMLTableRowElement>) {
|
|
196
|
+
if (!handler) return undefined;
|
|
197
|
+
return (event: React.MouseEvent<HTMLTableRowElement>) => {
|
|
198
|
+
if (!isRowClickIgnored(event)) handler(event);
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
181
202
|
return (
|
|
182
|
-
<tr
|
|
203
|
+
<tr
|
|
204
|
+
onClick={withRowClickGuard(onClick)}
|
|
205
|
+
onAuxClick={withRowClickGuard(onAuxClick)}
|
|
206
|
+
className={cx({
|
|
207
|
+
'cursor-pointer row-hover:bg-shader-neutral-light-8 dark:row-hover:bg-shader-neutral-dark-8 inner-link:-mx-xxs inner-link:rounded inner-link:px-xxs inner-link:transition-colors inner-link-hover:bg-iota-primary-90/30 inner-link-hover:ring-1 inner-link-hover:ring-iota-primary-70/60 dark:inner-link-hover:bg-iota-primary-70/30':
|
|
208
|
+
onClick,
|
|
209
|
+
})}
|
|
210
|
+
>
|
|
183
211
|
{leading}
|
|
184
212
|
{children}
|
|
185
213
|
</tr>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Copyright (c) 2026 IOTA Stiftung
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
export const INTERACTIVE_ELEMENT_SELECTOR = [
|
|
5
|
+
'a',
|
|
6
|
+
'button',
|
|
7
|
+
'input',
|
|
8
|
+
'select',
|
|
9
|
+
'textarea',
|
|
10
|
+
'label',
|
|
11
|
+
'[role="button"]',
|
|
12
|
+
'[role="checkbox"]',
|
|
13
|
+
'[role="link"]',
|
|
14
|
+
'[role="switch"]',
|
|
15
|
+
].join(',');
|
|
16
|
+
|
|
17
|
+
export const ROW_LINK_ATTRIBUTE = 'data-row-link';
|
|
18
|
+
|
|
19
|
+
export const ROW_LINK_PROPS = { [ROW_LINK_ATTRIBUTE]: true } as const;
|
package/src/lib/styles/base.css
CHANGED
|
@@ -40,8 +40,9 @@
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
:root.names {
|
|
43
|
-
--roboto-flex-variation-settings:
|
|
44
|
-
'
|
|
43
|
+
--roboto-flex-variation-settings:
|
|
44
|
+
'GRAD' 0, 'XOPQ' 96, 'XTRA' 468, 'YOPQ' 79, 'YTAS' 750, 'YTDE' -203, 'YTFI' 738,
|
|
45
|
+
'YTLC' 514, 'YTUC' 640, 'slnt' 0, 'wdth' 130;
|
|
45
46
|
|
|
46
47
|
.text-display-lg,
|
|
47
48
|
.text-display-md,
|
|
@@ -16,13 +16,13 @@ import {
|
|
|
16
16
|
generateVariableSpacing,
|
|
17
17
|
} from './constants';
|
|
18
18
|
import { THEMED_CUSTOM_COLORS } from './constants/customColors.constants';
|
|
19
|
-
import { firefoxPlugin, namesVariant } from './plugins';
|
|
19
|
+
import { firefoxPlugin, namesVariant, tableRowVariants } from './plugins';
|
|
20
20
|
import { NAMES_GRADIENTS } from './constants/gradients.constants';
|
|
21
21
|
|
|
22
22
|
export const BASE_CONFIG: Config = {
|
|
23
23
|
content: ['./src/**/*.{html,js,jsx,ts,tsx}'],
|
|
24
24
|
darkMode: 'selector',
|
|
25
|
-
plugins: [firefoxPlugin, namesVariant],
|
|
25
|
+
plugins: [firefoxPlugin, namesVariant, tableRowVariants],
|
|
26
26
|
theme: {
|
|
27
27
|
extend: {
|
|
28
28
|
fontSize: {
|