@iota/apps-ui-kit 0.12.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 +14 -0
- package/dist/index.d.ts +11 -2
- package/dist/index.es.js +1194 -1153
- 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/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 +2 -2
- package/src/lib/components/atoms/tooltip/Tooltip.tsx +50 -9
- package/src/lib/components/molecules/card/CardAction.tsx +1 -1
- package/src/lib/components/molecules/card/CardBody.tsx +1 -1
- package/src/lib/components/molecules/select/Select.tsx +1 -2
- package/src/lib/components/organisms/table/Table.tsx +21 -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",
|
|
@@ -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>
|
|
@@ -119,12 +119,12 @@ export function KeyValueInfo({
|
|
|
119
119
|
)}
|
|
120
120
|
>
|
|
121
121
|
<div
|
|
122
|
-
className={cx('flex shrink-0 flex-row items-center gap-x-0.5', {
|
|
122
|
+
className={cx('flex max-w-full shrink-0 flex-row items-center gap-x-0.5', {
|
|
123
123
|
'w-1/4': !fullwidth && !isReceiptLayout,
|
|
124
124
|
})}
|
|
125
125
|
>
|
|
126
126
|
{keyIcon}
|
|
127
|
-
<span className="key-value-key-text-color break-
|
|
127
|
+
<span className="key-value-key-text-color min-w-0 break-words text-body-md">
|
|
128
128
|
{keyText}
|
|
129
129
|
</span>
|
|
130
130
|
{tooltipText && (
|
|
@@ -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,7 +56,7 @@ export function Tooltip({
|
|
|
49
56
|
closeTimer.current = setTimeout(() => setVisible(false), closeDelay);
|
|
50
57
|
};
|
|
51
58
|
|
|
52
|
-
const computePosition = (rect: DOMRect) =>
|
|
59
|
+
const computePosition = (rect: DOMRect, placement: TooltipPosition) =>
|
|
53
60
|
({
|
|
54
61
|
[TooltipPosition.Top]: {
|
|
55
62
|
top: rect.top - offset,
|
|
@@ -79,7 +86,7 @@ export function Tooltip({
|
|
|
79
86
|
anchorX: 0,
|
|
80
87
|
anchorY: 0.5,
|
|
81
88
|
},
|
|
82
|
-
})[
|
|
89
|
+
})[placement];
|
|
83
90
|
|
|
84
91
|
const clampDelta = (boxStart: number, size: number, viewportSize: number, margin: number) => {
|
|
85
92
|
const boxEnd = boxStart + size;
|
|
@@ -95,11 +102,23 @@ export function Tooltip({
|
|
|
95
102
|
const tooltipEl = tooltipRef.current;
|
|
96
103
|
if (!rect || !tooltipEl) return;
|
|
97
104
|
|
|
98
|
-
const pos = computePosition(rect);
|
|
99
|
-
tooltipEl.style.transform = pos.transform;
|
|
100
|
-
|
|
101
105
|
const { width, height } = tooltipEl.getBoundingClientRect();
|
|
102
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;
|
|
103
122
|
|
|
104
123
|
const boxLeft = pos.left - width * pos.anchorX;
|
|
105
124
|
const boxTop = pos.top - height * pos.anchorY;
|
|
@@ -107,7 +126,15 @@ export function Tooltip({
|
|
|
107
126
|
const deltaX = clampDelta(boxLeft, width, window.innerWidth, margin);
|
|
108
127
|
const deltaY = clampDelta(boxTop, height, window.innerHeight, margin);
|
|
109
128
|
|
|
110
|
-
|
|
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 });
|
|
111
138
|
}, [visible, position, offset]);
|
|
112
139
|
|
|
113
140
|
useLayoutEffect(() => {
|
|
@@ -124,6 +151,15 @@ export function Tooltip({
|
|
|
124
151
|
};
|
|
125
152
|
}, [visible]);
|
|
126
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
|
+
|
|
127
163
|
// z-[9999999999]: needed because we must exceed the popup’s ≈2 147 483 647 z-index;
|
|
128
164
|
// otherwise the tooltip renders but stays invisible inside a Chrome extension
|
|
129
165
|
const base = 'z-[9999999999] w-max rounded p-xs tooltip-bg tooltip-text-color';
|
|
@@ -137,6 +173,7 @@ export function Tooltip({
|
|
|
137
173
|
onFocus={open}
|
|
138
174
|
onMouseLeave={close}
|
|
139
175
|
onBlur={close}
|
|
176
|
+
onClick={handleTriggerClick}
|
|
140
177
|
>
|
|
141
178
|
{children}
|
|
142
179
|
</div>
|
|
@@ -153,7 +190,11 @@ export function Tooltip({
|
|
|
153
190
|
transition: 'opacity .15s ease',
|
|
154
191
|
opacity: 1,
|
|
155
192
|
}}
|
|
156
|
-
className={cx(
|
|
193
|
+
className={cx(
|
|
194
|
+
base,
|
|
195
|
+
maxWidth,
|
|
196
|
+
coords.overlapsTrigger && 'pointer-events-none',
|
|
197
|
+
)}
|
|
157
198
|
onMouseEnter={open}
|
|
158
199
|
onMouseLeave={close}
|
|
159
200
|
>
|
|
@@ -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;
|
|
@@ -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
|
*/
|
|
@@ -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,19 +175,36 @@ 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,
|
|
181
190
|
}: PropsWithChildren<{
|
|
182
191
|
leading?: React.ReactNode;
|
|
183
192
|
onClick?: React.MouseEventHandler<HTMLTableRowElement>;
|
|
193
|
+
onAuxClick?: React.MouseEventHandler<HTMLTableRowElement>;
|
|
184
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
|
+
|
|
185
202
|
return (
|
|
186
203
|
<tr
|
|
187
|
-
onClick={onClick}
|
|
204
|
+
onClick={withRowClickGuard(onClick)}
|
|
205
|
+
onAuxClick={withRowClickGuard(onAuxClick)}
|
|
188
206
|
className={cx({
|
|
189
|
-
'cursor-pointer hover:bg-shader-neutral-light-8 dark:hover:bg-shader-neutral-dark-8':
|
|
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':
|
|
190
208
|
onClick,
|
|
191
209
|
})}
|
|
192
210
|
>
|
|
@@ -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: {
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
4
|
import plugin from 'tailwindcss/plugin';
|
|
5
|
+
import {
|
|
6
|
+
INTERACTIVE_ELEMENT_SELECTOR,
|
|
7
|
+
ROW_LINK_ATTRIBUTE,
|
|
8
|
+
} from '../constants/interactive.constants';
|
|
5
9
|
|
|
6
10
|
export const firefoxPlugin = plugin(({ addVariant }) => {
|
|
7
11
|
addVariant('firefox', '@-moz-document url-prefix()');
|
|
@@ -10,3 +14,13 @@ export const firefoxPlugin = plugin(({ addVariant }) => {
|
|
|
10
14
|
export const namesVariant = plugin(({ addVariant }) => {
|
|
11
15
|
addVariant('names', '&:is(.names *)');
|
|
12
16
|
});
|
|
17
|
+
|
|
18
|
+
export const tableRowVariants = plugin(({ addVariant }) => {
|
|
19
|
+
const innerLink = `a:not([${ROW_LINK_ATTRIBUTE}])`;
|
|
20
|
+
addVariant(
|
|
21
|
+
'row-hover',
|
|
22
|
+
`&:hover:not(:has(:is(${INTERACTIVE_ELEMENT_SELECTOR}):not([${ROW_LINK_ATTRIBUTE}]):hover))`,
|
|
23
|
+
);
|
|
24
|
+
addVariant('inner-link', `& ${innerLink}`);
|
|
25
|
+
addVariant('inner-link-hover', `& ${innerLink}:hover`);
|
|
26
|
+
});
|
|
@@ -12,7 +12,7 @@ const meta: Meta<typeof Panel> = {
|
|
|
12
12
|
<Panel {...props}>
|
|
13
13
|
<div className="flex flex-col items-start gap-2">
|
|
14
14
|
<Title title="Title" subtitle="subtitle" />
|
|
15
|
-
<div className="
|
|
15
|
+
<div className="px-md--rs pb-sm--rs">
|
|
16
16
|
<Address text="0x0d7...3f34" isCopyable />
|
|
17
17
|
<Address text="0x0d7...3f35" isCopyable />
|
|
18
18
|
<Address text="0x0d7...3f36" isCopyable />
|