@iota/apps-ui-kit 0.11.0 → 0.12.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 +13 -0
- package/dist/index.d.ts +33 -13
- package/dist/index.es.js +115 -89
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +4 -4
- package/dist/index.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- 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/key-value-info/KeyValueInfo.tsx +18 -3
- package/src/lib/components/atoms/tooltip/Tooltip.tsx +45 -18
- package/src/lib/components/molecules/card/CardAction.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/table-cell/TableCell.tsx +6 -0
- package/src/lib/components/organisms/accordion/Accordion.tsx +10 -1
- package/src/lib/components/organisms/table/Table.tsx +12 -2
package/package.json
CHANGED
|
@@ -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
|
);
|
|
@@ -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;
|
|
@@ -113,7 +120,7 @@ export function KeyValueInfo({
|
|
|
113
120
|
>
|
|
114
121
|
<div
|
|
115
122
|
className={cx('flex shrink-0 flex-row items-center gap-x-0.5', {
|
|
116
|
-
'w-1/4': !fullwidth,
|
|
123
|
+
'w-1/4': !fullwidth && !isReceiptLayout,
|
|
117
124
|
})}
|
|
118
125
|
>
|
|
119
126
|
{keyIcon}
|
|
@@ -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
|
>
|
|
@@ -49,51 +49,78 @@ export function Tooltip({
|
|
|
49
49
|
closeTimer.current = setTimeout(() => setVisible(false), closeDelay);
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const rect = triggerRef.current?.getBoundingClientRect();
|
|
56
|
-
if (!rect) return;
|
|
57
|
-
|
|
58
|
-
const pos = {
|
|
52
|
+
const computePosition = (rect: DOMRect) =>
|
|
53
|
+
({
|
|
59
54
|
[TooltipPosition.Top]: {
|
|
60
55
|
top: rect.top - offset,
|
|
61
56
|
left: rect.left + rect.width / 2,
|
|
62
57
|
transform: 'translate(-50%, -100%)',
|
|
58
|
+
anchorX: 0.5,
|
|
59
|
+
anchorY: 1,
|
|
63
60
|
},
|
|
64
61
|
[TooltipPosition.Bottom]: {
|
|
65
62
|
top: rect.bottom + offset,
|
|
66
63
|
left: rect.left + rect.width / 2,
|
|
67
64
|
transform: 'translate(-50%, 0)',
|
|
65
|
+
anchorX: 0.5,
|
|
66
|
+
anchorY: 0,
|
|
68
67
|
},
|
|
69
68
|
[TooltipPosition.Left]: {
|
|
70
69
|
top: rect.top + rect.height / 2,
|
|
71
70
|
left: rect.left - offset,
|
|
72
71
|
transform: 'translate(-100%, -50%)',
|
|
72
|
+
anchorX: 1,
|
|
73
|
+
anchorY: 0.5,
|
|
73
74
|
},
|
|
74
75
|
[TooltipPosition.Right]: {
|
|
75
76
|
top: rect.top + rect.height / 2,
|
|
76
77
|
left: rect.right + offset,
|
|
77
78
|
transform: 'translate(0, -50%)',
|
|
79
|
+
anchorX: 0,
|
|
80
|
+
anchorY: 0.5,
|
|
78
81
|
},
|
|
79
|
-
}[position];
|
|
82
|
+
})[position];
|
|
83
|
+
|
|
84
|
+
const clampDelta = (boxStart: number, size: number, viewportSize: number, margin: number) => {
|
|
85
|
+
const boxEnd = boxStart + size;
|
|
86
|
+
if (boxEnd > viewportSize - margin) return viewportSize - margin - boxEnd;
|
|
87
|
+
if (boxStart < margin) return margin - boxStart;
|
|
88
|
+
return 0;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
useLayoutEffect(() => {
|
|
92
|
+
if (!visible) return;
|
|
93
|
+
|
|
94
|
+
const rect = triggerRef.current?.getBoundingClientRect();
|
|
95
|
+
const tooltipEl = tooltipRef.current;
|
|
96
|
+
if (!rect || !tooltipEl) return;
|
|
97
|
+
|
|
98
|
+
const pos = computePosition(rect);
|
|
99
|
+
tooltipEl.style.transform = pos.transform;
|
|
100
|
+
|
|
101
|
+
const { width, height } = tooltipEl.getBoundingClientRect();
|
|
102
|
+
const margin = 8;
|
|
103
|
+
|
|
104
|
+
const boxLeft = pos.left - width * pos.anchorX;
|
|
105
|
+
const boxTop = pos.top - height * pos.anchorY;
|
|
106
|
+
|
|
107
|
+
const deltaX = clampDelta(boxLeft, width, window.innerWidth, margin);
|
|
108
|
+
const deltaY = clampDelta(boxTop, height, window.innerHeight, margin);
|
|
80
109
|
|
|
81
|
-
setCoords({ top: pos.top, left: pos.left });
|
|
82
|
-
tooltipRef.current!.style.transform = pos.transform;
|
|
110
|
+
setCoords({ top: pos.top + deltaY, left: pos.left + deltaX });
|
|
83
111
|
}, [visible, position, offset]);
|
|
84
112
|
|
|
85
113
|
useLayoutEffect(() => {
|
|
86
114
|
if (!visible) return;
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
setCoords((prev) => ({ ...prev, left: rect.left + rect.width / 2 }));
|
|
115
|
+
const dismiss = () => {
|
|
116
|
+
clearTimers();
|
|
117
|
+
setVisible(false);
|
|
91
118
|
};
|
|
92
|
-
window.addEventListener('scroll',
|
|
93
|
-
window.addEventListener('resize',
|
|
119
|
+
window.addEventListener('scroll', dismiss, true);
|
|
120
|
+
window.addEventListener('resize', dismiss);
|
|
94
121
|
return () => {
|
|
95
|
-
window.removeEventListener('scroll',
|
|
96
|
-
window.removeEventListener('resize',
|
|
122
|
+
window.removeEventListener('scroll', dismiss, true);
|
|
123
|
+
window.removeEventListener('resize', dismiss);
|
|
97
124
|
};
|
|
98
125
|
}, [visible]);
|
|
99
126
|
|
|
@@ -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;
|
|
@@ -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,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
|
},
|
|
@@ -177,9 +177,19 @@ export function TableHeader({ children }: PropsWithChildren): JSX.Element {
|
|
|
177
177
|
export function TableRow({
|
|
178
178
|
children,
|
|
179
179
|
leading,
|
|
180
|
-
|
|
180
|
+
onClick,
|
|
181
|
+
}: PropsWithChildren<{
|
|
182
|
+
leading?: React.ReactNode;
|
|
183
|
+
onClick?: React.MouseEventHandler<HTMLTableRowElement>;
|
|
184
|
+
}>): JSX.Element {
|
|
181
185
|
return (
|
|
182
|
-
<tr
|
|
186
|
+
<tr
|
|
187
|
+
onClick={onClick}
|
|
188
|
+
className={cx({
|
|
189
|
+
'cursor-pointer hover:bg-shader-neutral-light-8 dark:hover:bg-shader-neutral-dark-8':
|
|
190
|
+
onClick,
|
|
191
|
+
})}
|
|
192
|
+
>
|
|
183
193
|
{leading}
|
|
184
194
|
{children}
|
|
185
195
|
</tr>
|