@iota/apps-ui-kit 0.5.1 → 0.6.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.5.1",
3
+ "version": "0.6.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.3.0"
33
+ "@iota/apps-ui-icons": "0.3.1"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@chromatic-com/storybook": "^1.6.0",
@@ -43,6 +43,7 @@
43
43
  "@types/lodash.merge": "^4.6.9",
44
44
  "@types/node": "^20.14.10",
45
45
  "@types/react": "^18.3.3",
46
+ "@types/react-dom": "^18.3.0",
46
47
  "@vitejs/plugin-react-swc": "^3.3.2",
47
48
  "autoprefixer": "^10.4.19",
48
49
  "cross-env": "^7.0.3",
@@ -38,6 +38,10 @@ interface ButtonSegmentProps {
38
38
  * The type of the button.
39
39
  */
40
40
  type?: ButtonSegmentType;
41
+ /**
42
+ * If the button is nested inside a parent button.
43
+ */
44
+ isNested?: boolean;
41
45
  }
42
46
 
43
47
  export function ButtonSegment({
@@ -47,6 +51,7 @@ export function ButtonSegment({
47
51
  disabled,
48
52
  onClick,
49
53
  type = ButtonSegmentType.Rounded,
54
+ isNested = false,
50
55
  }: ButtonSegmentProps): React.JSX.Element {
51
56
  const isUnderlined = type === ButtonSegmentType.Underlined;
52
57
 
@@ -58,9 +63,16 @@ export function ButtonSegment({
58
63
 
59
64
  const underlined = isUnderlined ? (selected ? UNDERLINED_SELECTED : UNDERLINED) : '';
60
65
  const textColors = selected ? TEXT_COLORS_SELECTED : TEXT_COLORS;
61
- const padding = isUnderlined ? 'px-lg py-md' : 'px-sm py-[6px]';
66
+ const padding = isUnderlined ? (isNested ? 'px-md py-sm' : 'px-lg py-md') : 'px-sm py-[6px]';
62
67
  const borderRadius = isUnderlined ? '' : 'rounded-full';
63
- const textSize = isUnderlined ? 'text-title-md' : 'text-label-lg';
68
+ const textSize = isNested
69
+ ? isUnderlined
70
+ ? 'text-title-sm'
71
+ : 'text-label-md'
72
+ : isUnderlined
73
+ ? 'text-title-md'
74
+ : 'text-label-lg';
75
+
64
76
  return (
65
77
  <ButtonUnstyled
66
78
  onClick={onClick}
@@ -79,7 +91,7 @@ export function ButtonSegment({
79
91
  >
80
92
  <div className={cx('flex flex-row items-center justify-center gap-2', textSize)}>
81
93
  {icon && <span>{icon}</span>}
82
- <span className="font-inter">{label}</span>
94
+ <span className="shrink-0 font-inter">{label}</span>
83
95
  </div>
84
96
  </ButtonUnstyled>
85
97
  );
@@ -1,37 +1,138 @@
1
1
  // Copyright (c) 2024 IOTA Stiftung
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
+ import { useRef, useState, useLayoutEffect } from 'react';
4
5
  import type { PropsWithChildren } from 'react';
6
+ import { createPortal } from 'react-dom';
5
7
  import cx from 'classnames';
6
8
  import { TooltipPosition } from './tooltip.enums';
7
- import { TOOLTIP_POSITION } from './tooltip.classes';
8
9
 
9
10
  interface TooltipProps {
10
11
  text: string;
11
12
  position?: TooltipPosition;
12
13
  maxWidth?: string;
14
+ offset?: number;
15
+ openDelay?: number;
16
+ closeDelay?: number;
13
17
  }
14
18
 
15
19
  export function Tooltip({
16
20
  text,
17
21
  position = TooltipPosition.Top,
18
22
  maxWidth = 'max-w-[200px]',
23
+ offset = 8,
24
+ openDelay = 0,
25
+ closeDelay = 100,
19
26
  children,
20
- }: PropsWithChildren<TooltipProps>): React.JSX.Element {
21
- const tooltipPositionClass = TOOLTIP_POSITION[position];
27
+ }: PropsWithChildren<TooltipProps>) {
28
+ const triggerRef = useRef<HTMLDivElement>(null);
29
+ const tooltipRef = useRef<HTMLDivElement>(null);
30
+
31
+ const [visible, setVisible] = useState(false);
32
+ const [coords, setCoords] = useState<{ top: number; left: number }>({ top: 0, left: 0 });
33
+
34
+ const openTimer = useRef<ReturnType<typeof setTimeout>>();
35
+ const closeTimer = useRef<ReturnType<typeof setTimeout>>();
36
+
37
+ const clearTimers = () => {
38
+ clearTimeout(openTimer.current);
39
+ clearTimeout(closeTimer.current);
40
+ };
41
+
42
+ const open = () => {
43
+ clearTimers();
44
+ openTimer.current = setTimeout(() => setVisible(true), openDelay);
45
+ };
46
+
47
+ const close = () => {
48
+ clearTimers();
49
+ closeTimer.current = setTimeout(() => setVisible(false), closeDelay);
50
+ };
51
+
52
+ useLayoutEffect(() => {
53
+ if (!visible) return;
54
+
55
+ const rect = triggerRef.current?.getBoundingClientRect();
56
+ if (!rect) return;
57
+
58
+ const pos = {
59
+ [TooltipPosition.Top]: {
60
+ top: rect.top - offset,
61
+ left: rect.left + rect.width / 2,
62
+ transform: 'translate(-50%, -100%)',
63
+ },
64
+ [TooltipPosition.Bottom]: {
65
+ top: rect.bottom + offset,
66
+ left: rect.left + rect.width / 2,
67
+ transform: 'translate(-50%, 0)',
68
+ },
69
+ [TooltipPosition.Left]: {
70
+ top: rect.top + rect.height / 2,
71
+ left: rect.left - offset,
72
+ transform: 'translate(-100%, -50%)',
73
+ },
74
+ [TooltipPosition.Right]: {
75
+ top: rect.top + rect.height / 2,
76
+ left: rect.right + offset,
77
+ transform: 'translate(0, -50%)',
78
+ },
79
+ }[position];
80
+
81
+ setCoords({ top: pos.top, left: pos.left });
82
+ tooltipRef.current!.style.transform = pos.transform;
83
+ }, [visible, position, offset]);
84
+
85
+ useLayoutEffect(() => {
86
+ if (!visible) return;
87
+ const update = () => {
88
+ const rect = triggerRef.current?.getBoundingClientRect();
89
+ if (!rect) return;
90
+ setCoords((prev) => ({ ...prev, left: rect.left + rect.width / 2 }));
91
+ };
92
+ window.addEventListener('scroll', update, true);
93
+ window.addEventListener('resize', update);
94
+ return () => {
95
+ window.removeEventListener('scroll', update, true);
96
+ window.removeEventListener('resize', update);
97
+ };
98
+ }, [visible]);
99
+
100
+ const base =
101
+ 'z-[999] w-max rounded bg-neutral-80 p-xs text-neutral-10 dark:bg-neutral-30 dark:text-neutral-92';
102
+
22
103
  return (
23
- <div className="group/tooltip relative inline-block">
24
- {children}
104
+ <>
25
105
  <div
26
- className={cx(
27
- 'absolute z-[999] hidden w-max rounded bg-neutral-80 p-xs text-neutral-10 opacity-0 transition-opacity duration-300 group-hover/tooltip:block group-hover/tooltip:opacity-100 group-focus/tooltip:opacity-100 dark:bg-neutral-30 dark:text-neutral-92',
28
- tooltipPositionClass,
29
- maxWidth,
30
- )}
31
- role="tooltip"
106
+ ref={triggerRef}
107
+ className="inline-block cursor-pointer"
108
+ onMouseEnter={open}
109
+ onFocus={open}
110
+ onMouseLeave={close}
111
+ onBlur={close}
32
112
  >
33
- <p className="w-full break-words">{text}</p>
113
+ {children}
34
114
  </div>
35
- </div>
115
+
116
+ {visible &&
117
+ createPortal(
118
+ <div
119
+ ref={tooltipRef}
120
+ role="tooltip"
121
+ style={{
122
+ position: 'fixed',
123
+ top: coords.top,
124
+ left: coords.left,
125
+ transition: 'opacity .15s ease',
126
+ opacity: 1,
127
+ }}
128
+ className={cx(base, maxWidth)}
129
+ onMouseEnter={open}
130
+ onMouseLeave={close}
131
+ >
132
+ <p className="w-full break-words">{text}</p>
133
+ </div>,
134
+ document.body,
135
+ )}
136
+ </>
36
137
  );
37
138
  }
@@ -26,8 +26,12 @@ export function SegmentedButton({
26
26
  const backgroundColors = BACKGROUND_COLORS[type];
27
27
  const borderColors = type === SegmentedButtonType.Outlined ? OUTLINED_BORDER : '';
28
28
  const borderShape = shape === ButtonSegmentType.Rounded ? 'rounded-full gap-1 p-xxs' : '';
29
+ const scrollable =
30
+ shape === ButtonSegmentType.Rounded ? 'flex-wrap' : 'flex-nowrap overflow-x-auto';
29
31
  return (
30
- <div className={cx('flex flex-row flex-wrap', backgroundColors, borderColors, borderShape)}>
32
+ <div
33
+ className={cx('flex flex-row', backgroundColors, borderColors, borderShape, scrollable)}
34
+ >
31
35
  {children}
32
36
  </div>
33
37
  );
@@ -103,9 +103,9 @@ export function Table({
103
103
  </table>
104
104
  </div>
105
105
  <div
106
- className={cx('flex w-full items-center gap-2 pt-md', {
106
+ className={cx('flex w-full items-center gap-sm pt-md', {
107
107
  hidden: !supportingLabel && !paginationOptions && !action,
108
- 'justify-between': paginationOptions,
108
+ 'flex-col justify-between sm:flex-row': paginationOptions,
109
109
  'justify-end': !paginationOptions && action,
110
110
  })}
111
111
  >
@@ -24,7 +24,7 @@ const responsivePreset: Config = merge({}, BASE_CONFIG, {
24
24
  center: true,
25
25
  screens,
26
26
  padding: {
27
- DEFAULT: pxToRem(24),
27
+ DEFAULT: pxToRem(20),
28
28
  md: pxToRem(48),
29
29
  lg: pxToRem(120),
30
30
  xl: pxToRem(240),