@dxos/react-ui 0.9.0 → 0.9.1-main.c7dcc2e112
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/dist/lib/browser/{chunk-7JFD5ZHZ.mjs → chunk-F3PLXTDA.mjs} +21 -25
- package/dist/lib/browser/{chunk-7JFD5ZHZ.mjs.map → chunk-F3PLXTDA.mjs.map} +3 -3
- package/dist/lib/browser/{chunk-5SPBSIWS.mjs → chunk-HFJYTGHU.mjs} +11 -1
- package/dist/lib/browser/chunk-HFJYTGHU.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1042 -856
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +5 -5
- package/dist/lib/browser/testing/index.mjs.map +2 -2
- package/dist/lib/browser/translations.mjs +1 -1
- package/dist/lib/node-esm/{chunk-TTEL2FP2.mjs → chunk-LLVUYLAM.mjs} +21 -25
- package/dist/lib/node-esm/{chunk-TTEL2FP2.mjs.map → chunk-LLVUYLAM.mjs.map} +3 -3
- package/dist/lib/node-esm/{chunk-FPVTVXND.mjs → chunk-OJYUPUCY.mjs} +11 -1
- package/dist/lib/node-esm/chunk-OJYUPUCY.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1042 -856
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +5 -5
- package/dist/lib/node-esm/testing/index.mjs.map +2 -2
- package/dist/lib/node-esm/translations.mjs +1 -1
- package/dist/types/src/components/Button/Button.d.ts +2 -0
- package/dist/types/src/components/Button/Button.d.ts.map +1 -1
- package/dist/types/src/components/Button/IconButton.d.ts +0 -1
- package/dist/types/src/components/Button/IconButton.d.ts.map +1 -1
- package/dist/types/src/components/Button/IconButton.stories.d.ts +34 -0
- package/dist/types/src/components/Button/IconButton.stories.d.ts.map +1 -1
- package/dist/types/src/components/Button/SystemIconButton.d.ts +20 -0
- package/dist/types/src/components/Button/SystemIconButton.d.ts.map +1 -0
- package/dist/types/src/components/Button/ToggleIconButton.d.ts +15 -0
- package/dist/types/src/components/Button/ToggleIconButton.d.ts.map +1 -0
- package/dist/types/src/components/Button/index.d.ts +2 -0
- package/dist/types/src/components/Button/index.d.ts.map +1 -1
- package/dist/types/src/components/Card/Card.d.ts +25 -19
- package/dist/types/src/components/Card/Card.d.ts.map +1 -1
- package/dist/types/src/components/Card/Card.stories.d.ts +1 -1
- package/dist/types/src/components/Card/Card.stories.d.ts.map +1 -1
- package/dist/types/src/components/Card/Card.theme.d.ts.map +1 -1
- package/dist/types/src/components/Clipboard/index.d.ts +1 -0
- package/dist/types/src/components/Clipboard/index.d.ts.map +1 -1
- package/dist/types/src/components/Column/Column.d.ts +19 -1
- package/dist/types/src/components/Column/Column.d.ts.map +1 -1
- package/dist/types/src/components/Column/Column.theme.d.ts +8 -0
- package/dist/types/src/components/Column/Column.theme.d.ts.map +1 -1
- package/dist/types/src/components/Input/constants.d.ts +3 -0
- package/dist/types/src/components/Input/constants.d.ts.map +1 -1
- package/dist/types/src/components/Message/Message.theme.d.ts.map +1 -1
- package/dist/types/src/components/Toast/Toast.d.ts.map +1 -1
- package/dist/types/src/components/Toast/Toast.theme.d.ts.map +1 -1
- package/dist/types/src/components/Tooltip/TextTooltip.d.ts +19 -0
- package/dist/types/src/components/Tooltip/TextTooltip.d.ts.map +1 -0
- package/dist/types/src/components/Tooltip/index.d.ts +1 -0
- package/dist/types/src/components/Tooltip/index.d.ts.map +1 -1
- package/dist/types/src/testing/Loading.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +10 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +16 -16
- package/src/components/Button/Button.tsx +23 -2
- package/src/components/Button/IconButton.stories.tsx +81 -1
- package/src/components/Button/IconButton.tsx +2 -4
- package/src/components/Button/SystemIconButton.tsx +153 -0
- package/src/components/Button/ToggleIconButton.tsx +32 -0
- package/src/components/Button/index.ts +2 -0
- package/src/components/Card/Card.stories.tsx +69 -27
- package/src/components/Card/Card.theme.ts +27 -9
- package/src/components/Card/Card.tsx +165 -88
- package/src/components/Column/Column.theme.ts +32 -4
- package/src/components/Column/Column.tsx +36 -1
- package/src/components/Input/constants.ts +3 -0
- package/src/components/Message/Message.theme.ts +0 -10
- package/src/components/Message/Message.tsx +4 -4
- package/src/components/Popover/Popover.theme.ts +1 -1
- package/src/components/Toast/Toast.theme.ts +0 -6
- package/src/components/Toast/Toast.tsx +11 -10
- package/src/components/Tooltip/TextTooltip.tsx +54 -0
- package/src/components/Tooltip/index.ts +1 -0
- package/src/testing/Loading.tsx +2 -1
- package/src/testing/decorators/withLayout.tsx +2 -2
- package/src/translations.ts +10 -0
- package/dist/lib/browser/chunk-5SPBSIWS.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-FPVTVXND.mjs.map +0 -7
|
@@ -5,25 +5,23 @@
|
|
|
5
5
|
import { Primitive } from '@radix-ui/react-primitive';
|
|
6
6
|
import { Slot } from '@radix-ui/react-slot';
|
|
7
7
|
import DOMPurify from 'dompurify';
|
|
8
|
-
import React, { CSSProperties,
|
|
8
|
+
import React, { CSSProperties, MouseEventHandler, type ReactNode, forwardRef, useId, useMemo } from 'react';
|
|
9
|
+
import { useTranslation } from 'react-i18next';
|
|
9
10
|
|
|
10
11
|
import { iconSize } from '@dxos/ui-theme';
|
|
11
|
-
import { type Density } from '@dxos/ui-types';
|
|
12
|
+
import { type Density, type SlottableProps } from '@dxos/ui-types';
|
|
13
|
+
|
|
14
|
+
import { translationKey } from '#translations';
|
|
12
15
|
|
|
13
16
|
import { useThemeContext } from '../../hooks';
|
|
14
17
|
import { composable, composableProps, slottable } from '../../util';
|
|
15
18
|
import { type ThemedClassName } from '../../util';
|
|
16
|
-
import { Button } from '../Button';
|
|
19
|
+
import { Button, IconButton } from '../Button';
|
|
17
20
|
import { Column } from '../Column';
|
|
18
|
-
import { Icon
|
|
21
|
+
import { Icon } from '../Icon';
|
|
19
22
|
import { Image, type ImageProps } from '../Image';
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
type ToolbarActionIconButtonProps,
|
|
23
|
-
ToolbarDragHandleProps,
|
|
24
|
-
type ToolbarMenuProps,
|
|
25
|
-
type ToolbarRootProps,
|
|
26
|
-
} from '../Toolbar';
|
|
23
|
+
import { DropdownMenu } from '../Menu';
|
|
24
|
+
import { type ToolbarActionIconButtonProps, type ToolbarDragHandleProps, type ToolbarMenuProps } from '../Toolbar';
|
|
27
25
|
|
|
28
26
|
//
|
|
29
27
|
// Root
|
|
@@ -82,22 +80,34 @@ CardRoot.displayName = CARD_ROOT_NAME;
|
|
|
82
80
|
|
|
83
81
|
const CARD_HEADER_NAME = 'Card.Header';
|
|
84
82
|
|
|
85
|
-
type CardHeaderProps =
|
|
83
|
+
type CardHeaderProps = SlottableProps;
|
|
86
84
|
|
|
87
85
|
/**
|
|
88
|
-
* Top
|
|
89
|
-
*
|
|
90
|
-
*
|
|
86
|
+
* Top header row of a Card. Renders as a `<header>` and shares `Card.Row`'s subgrid +
|
|
87
|
+
* `data-slot` layout: `Card.Block` children land in the gutters, anonymous children
|
|
88
|
+
* (e.g. `Card.Title`) in the center. Sets `--icon-size` 5 — header icons are larger than
|
|
89
|
+
* row icons (which use 4). Not a toolbar: most headers carry 0–1 controls, so `role="toolbar"`
|
|
90
|
+
* was inaccurate; controls are reached by normal tab order.
|
|
91
91
|
*/
|
|
92
|
-
const CardHeader =
|
|
93
|
-
|
|
92
|
+
const CardHeader = slottable<HTMLDivElement, CardHeaderProps>(
|
|
93
|
+
({ children, asChild, style, ...props }, forwardedRef) => {
|
|
94
|
+
const { tx } = useThemeContext();
|
|
95
|
+
const { className, ...rest } = composableProps(props);
|
|
96
|
+
// `@radix-ui/react-primitive` has no `header` node; the intrinsic element handles asChild via Slot.
|
|
97
|
+
const Comp = asChild ? Slot : 'header';
|
|
94
98
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
99
|
+
return (
|
|
100
|
+
<Comp
|
|
101
|
+
{...rest}
|
|
102
|
+
style={{ ...iconSize(5), ...style }}
|
|
103
|
+
className={tx('card.header', {}, className)}
|
|
104
|
+
ref={forwardedRef}
|
|
105
|
+
>
|
|
106
|
+
{children}
|
|
107
|
+
</Comp>
|
|
108
|
+
);
|
|
109
|
+
},
|
|
110
|
+
);
|
|
101
111
|
|
|
102
112
|
CardHeader.displayName = CARD_HEADER_NAME;
|
|
103
113
|
|
|
@@ -109,13 +119,27 @@ const CARD_DRAG_HANDLE_NAME = 'Card.DragHandle';
|
|
|
109
119
|
|
|
110
120
|
type CardDragHandleProps = ToolbarDragHandleProps;
|
|
111
121
|
|
|
112
|
-
const CardDragHandle = forwardRef<HTMLButtonElement, CardDragHandleProps>(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
122
|
+
const CardDragHandle = forwardRef<HTMLButtonElement, CardDragHandleProps>(
|
|
123
|
+
({ testId = 'drag-handle', label }, forwardedRef) => {
|
|
124
|
+
const { t } = useTranslation(translationKey);
|
|
125
|
+
return (
|
|
126
|
+
<CardBlock>
|
|
127
|
+
<IconButton
|
|
128
|
+
data-testid={testId}
|
|
129
|
+
tabIndex={-1}
|
|
130
|
+
noTooltip
|
|
131
|
+
iconOnly
|
|
132
|
+
icon='ph--dots-six-vertical--regular'
|
|
133
|
+
variant='ghost'
|
|
134
|
+
label={label ?? t('toolbar-drag-handle.label')}
|
|
135
|
+
classNames='dx-focus-ring-none cursor-pointer text-base-fg'
|
|
136
|
+
disabled={!forwardedRef}
|
|
137
|
+
ref={forwardedRef}
|
|
138
|
+
/>
|
|
139
|
+
</CardBlock>
|
|
140
|
+
);
|
|
141
|
+
},
|
|
142
|
+
);
|
|
119
143
|
|
|
120
144
|
CardDragHandle.displayName = CARD_DRAG_HANDLE_NAME;
|
|
121
145
|
|
|
@@ -127,13 +151,28 @@ const CARD_ACTION_ICON_BUTTON_NAME = 'Card.ActionIconButton';
|
|
|
127
151
|
|
|
128
152
|
type CardActionIconButtonProps = ToolbarActionIconButtonProps;
|
|
129
153
|
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
)
|
|
136
|
-
});
|
|
154
|
+
const CARD_ACTION_ICONS = { close: 'ph--x--regular', delete: 'ph--trash--regular' } as const;
|
|
155
|
+
|
|
156
|
+
const CARD_ACTION_LABEL_KEYS = { close: 'toolbar-close.label', delete: 'toolbar-delete.label' } as const;
|
|
157
|
+
|
|
158
|
+
const CardActionIconButton = forwardRef<HTMLButtonElement, CardActionIconButtonProps>(
|
|
159
|
+
({ action, onClick, label }, forwardedRef) => {
|
|
160
|
+
const { t } = useTranslation(translationKey);
|
|
161
|
+
return (
|
|
162
|
+
<CardBlock end>
|
|
163
|
+
<IconButton
|
|
164
|
+
iconOnly
|
|
165
|
+
icon={CARD_ACTION_ICONS[action]}
|
|
166
|
+
variant='ghost'
|
|
167
|
+
label={label ?? t(CARD_ACTION_LABEL_KEYS[action])}
|
|
168
|
+
classNames='cursor-pointer'
|
|
169
|
+
onClick={onClick}
|
|
170
|
+
ref={forwardedRef}
|
|
171
|
+
/>
|
|
172
|
+
</CardBlock>
|
|
173
|
+
);
|
|
174
|
+
},
|
|
175
|
+
);
|
|
137
176
|
|
|
138
177
|
CardActionIconButton.displayName = CARD_ACTION_ICON_BUTTON_NAME;
|
|
139
178
|
|
|
@@ -145,43 +184,67 @@ const CARD_MENU_NAME = 'Card.Menu';
|
|
|
145
184
|
|
|
146
185
|
type CardMenuProps<T extends any | void = void> = ToolbarMenuProps<T>;
|
|
147
186
|
|
|
148
|
-
function CardMenu<T extends any | void = void>({ context, items
|
|
187
|
+
function CardMenu<T extends any | void = void>({ context, items }: CardMenuProps<T>) {
|
|
188
|
+
const { t } = useTranslation(translationKey);
|
|
149
189
|
return (
|
|
150
|
-
<
|
|
151
|
-
<
|
|
152
|
-
|
|
190
|
+
<CardBlock end>
|
|
191
|
+
<DropdownMenu.Root>
|
|
192
|
+
<DropdownMenu.Trigger disabled={!items?.length} asChild>
|
|
193
|
+
<IconButton
|
|
194
|
+
iconOnly
|
|
195
|
+
variant='ghost'
|
|
196
|
+
icon='ph--dots-three-vertical--regular'
|
|
197
|
+
label={t('toolbar-menu.label')}
|
|
198
|
+
/>
|
|
199
|
+
</DropdownMenu.Trigger>
|
|
200
|
+
{(items?.length ?? 0) > 0 && (
|
|
201
|
+
<DropdownMenu.Portal>
|
|
202
|
+
<DropdownMenu.Content>
|
|
203
|
+
<DropdownMenu.Viewport>
|
|
204
|
+
{items?.map(({ label, onClick: onSelect }, index) => (
|
|
205
|
+
// `context` is the generic payload threaded to each handler; the cast is the
|
|
206
|
+
// generic boundary (T may be `void`, so `context` is typed `T | undefined`).
|
|
207
|
+
<DropdownMenu.Item key={index} onSelect={() => onSelect(context as T)}>
|
|
208
|
+
{label}
|
|
209
|
+
</DropdownMenu.Item>
|
|
210
|
+
))}
|
|
211
|
+
</DropdownMenu.Viewport>
|
|
212
|
+
<DropdownMenu.Arrow />
|
|
213
|
+
</DropdownMenu.Content>
|
|
214
|
+
</DropdownMenu.Portal>
|
|
215
|
+
)}
|
|
216
|
+
</DropdownMenu.Root>
|
|
217
|
+
</CardBlock>
|
|
153
218
|
);
|
|
154
219
|
}
|
|
155
220
|
|
|
156
221
|
CardMenu.displayName = CARD_MENU_NAME;
|
|
157
222
|
|
|
158
223
|
//
|
|
159
|
-
//
|
|
224
|
+
// Block
|
|
160
225
|
//
|
|
161
226
|
|
|
162
|
-
const
|
|
227
|
+
const CARD_BLOCK_NAME = 'Card.Block';
|
|
163
228
|
|
|
164
|
-
|
|
165
|
-
return (
|
|
166
|
-
<CardIconBlock>
|
|
167
|
-
<Icon {...props} />
|
|
168
|
-
</CardIconBlock>
|
|
169
|
-
);
|
|
170
|
-
}
|
|
229
|
+
type CardBlockProps = SlottableProps<{ end?: boolean; compact?: boolean; square?: boolean }>;
|
|
171
230
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const
|
|
231
|
+
/**
|
|
232
|
+
* Leading (default) or trailing (`end`) gutter slot of a Card row/header. Sized to the
|
|
233
|
+
* rail-item square so a passive `<Icon>` aligns with an `IconButton`. Defaults text color to
|
|
234
|
+
* `subdued` so a decorative `<Icon>` child needs no styling; interactive children set their own.
|
|
235
|
+
*/
|
|
236
|
+
const CardBlock = composable<HTMLDivElement, CardBlockProps>(({ children, ...props }, forwardedRef) => {
|
|
237
|
+
const { tx } = useThemeContext();
|
|
238
|
+
const { className, ...rest } = composableProps(props);
|
|
179
239
|
|
|
180
|
-
|
|
181
|
-
|
|
240
|
+
return (
|
|
241
|
+
<Column.Block {...rest} classNames={tx('card.block', {}, className)} ref={forwardedRef}>
|
|
242
|
+
{children}
|
|
243
|
+
</Column.Block>
|
|
244
|
+
);
|
|
182
245
|
});
|
|
183
246
|
|
|
184
|
-
|
|
247
|
+
CardBlock.displayName = CARD_BLOCK_NAME;
|
|
185
248
|
|
|
186
249
|
//
|
|
187
250
|
// Title
|
|
@@ -271,32 +334,28 @@ CardSection.displayName = CARD_SECTION_NAME;
|
|
|
271
334
|
|
|
272
335
|
const CARD_ROW_NAME = 'Card.Row';
|
|
273
336
|
|
|
274
|
-
type CardRowProps = {
|
|
337
|
+
type CardRowProps = { fullWidth?: boolean };
|
|
275
338
|
|
|
276
339
|
/**
|
|
277
340
|
* A row inside a Card.
|
|
278
|
-
* - Default: spans all 3 columns
|
|
279
|
-
*
|
|
280
|
-
* - `fullWidth`: spans all columns without a subgrid — children do their own internal layout
|
|
281
|
-
*
|
|
341
|
+
* - Default: spans all 3 columns as a subgrid; children align via `data-slot` — `Card.Block`
|
|
342
|
+
* lands in the gutters (start/end), anonymous children in the center. Sets `--icon-size` 4.
|
|
343
|
+
* - `fullWidth`: spans all columns without a subgrid — children do their own internal layout;
|
|
344
|
+
* `Card.Block` placement is inert in this mode.
|
|
282
345
|
*/
|
|
283
346
|
const CardRow = slottable<HTMLDivElement, CardRowProps>(
|
|
284
|
-
({ children, asChild,
|
|
347
|
+
({ children, asChild, fullWidth, style, ...props }, forwardedRef) => {
|
|
285
348
|
const { tx } = useThemeContext();
|
|
286
349
|
const { className, ...rest } = composableProps(props);
|
|
287
350
|
const Comp = asChild ? Slot : Primitive.div;
|
|
288
|
-
const icon =
|
|
289
|
-
typeof iconProp === 'string' ? (
|
|
290
|
-
<CardIcon classNames='text-subdued' icon={iconProp as string} size={4} />
|
|
291
|
-
) : iconProp ? (
|
|
292
|
-
iconProp
|
|
293
|
-
) : (
|
|
294
|
-
<div />
|
|
295
|
-
);
|
|
296
351
|
|
|
297
352
|
return (
|
|
298
|
-
<Comp
|
|
299
|
-
{
|
|
353
|
+
<Comp
|
|
354
|
+
{...rest}
|
|
355
|
+
style={{ ...iconSize(4), ...style }}
|
|
356
|
+
className={tx('card.row', { fullWidth }, className)}
|
|
357
|
+
ref={forwardedRef}
|
|
358
|
+
>
|
|
300
359
|
{children}
|
|
301
360
|
</Comp>
|
|
302
361
|
);
|
|
@@ -379,15 +438,21 @@ type CardPosterProps = ThemedClassName<
|
|
|
379
438
|
Omit<ImageProps, 'src' | 'alt' | 'classNames'>
|
|
380
439
|
>;
|
|
381
440
|
|
|
382
|
-
function CardPoster({
|
|
441
|
+
function CardPoster({
|
|
442
|
+
classNames,
|
|
443
|
+
alt,
|
|
444
|
+
aspect: aspectProp,
|
|
445
|
+
image,
|
|
446
|
+
icon,
|
|
447
|
+
fit = 'cover',
|
|
448
|
+
...imageProps
|
|
449
|
+
}: CardPosterProps) {
|
|
383
450
|
const { tx } = useThemeContext();
|
|
384
451
|
const aspect = aspectProp === 'auto' ? 'aspect-auto' : 'aspect-video';
|
|
385
452
|
|
|
386
453
|
if (image) {
|
|
387
454
|
return (
|
|
388
|
-
<
|
|
389
|
-
<Image classNames={[tx('card.poster', {}), aspect, classNames]} src={image} alt={alt} {...imageProps} />
|
|
390
|
-
</div>
|
|
455
|
+
<Image classNames={[tx('card.poster', {}), aspect, classNames]} src={image} alt={alt} fit={fit} {...imageProps} />
|
|
391
456
|
);
|
|
392
457
|
}
|
|
393
458
|
|
|
@@ -414,9 +479,17 @@ function CardAction({ icon, actionIcon = 'ph--arrow-right--regular', label, onCl
|
|
|
414
479
|
const { tx } = useThemeContext();
|
|
415
480
|
return (
|
|
416
481
|
<Button variant='ghost' classNames={tx('card.action', {})} onClick={onClick}>
|
|
417
|
-
{icon
|
|
418
|
-
|
|
419
|
-
|
|
482
|
+
{icon && (
|
|
483
|
+
<CardBlock>
|
|
484
|
+
<Icon icon={icon} size={4} />
|
|
485
|
+
</CardBlock>
|
|
486
|
+
)}
|
|
487
|
+
<span className={tx('card.action-label', {})}>{label}</span>
|
|
488
|
+
{actionIcon && (
|
|
489
|
+
<CardBlock end>
|
|
490
|
+
<Icon icon={actionIcon} size={4} />
|
|
491
|
+
</CardBlock>
|
|
492
|
+
)}
|
|
420
493
|
</Button>
|
|
421
494
|
);
|
|
422
495
|
}
|
|
@@ -435,9 +508,13 @@ function CardLink({ label, href }: CardLinkProps) {
|
|
|
435
508
|
const { tx } = useThemeContext();
|
|
436
509
|
return (
|
|
437
510
|
<a className={tx('card.link', {})} data-variant='ghost' href={href} target='_blank' rel='noreferrer'>
|
|
438
|
-
<
|
|
511
|
+
<CardBlock>
|
|
512
|
+
<Icon icon='ph--link--regular' size={4} />
|
|
513
|
+
</CardBlock>
|
|
439
514
|
<span className={tx('card.link-label', {})}>{label}</span>
|
|
440
|
-
<
|
|
515
|
+
<CardBlock end classNames='invisible group-hover:visible'>
|
|
516
|
+
<Icon icon='ph--arrow-square-out--regular' size={4} />
|
|
517
|
+
</CardBlock>
|
|
441
518
|
</a>
|
|
442
519
|
);
|
|
443
520
|
}
|
|
@@ -454,12 +531,11 @@ export const Card = {
|
|
|
454
531
|
// Header
|
|
455
532
|
Header: CardHeader,
|
|
456
533
|
|
|
457
|
-
// Header parts
|
|
458
|
-
|
|
534
|
+
// Header / row parts
|
|
535
|
+
Block: CardBlock,
|
|
459
536
|
DragHandle: CardDragHandle,
|
|
460
537
|
ActionIconButton: CardActionIconButton,
|
|
461
538
|
Menu: CardMenu,
|
|
462
|
-
Icon: CardIcon,
|
|
463
539
|
Title: CardTitle,
|
|
464
540
|
|
|
465
541
|
// Body
|
|
@@ -478,6 +554,7 @@ export const Card = {
|
|
|
478
554
|
export type {
|
|
479
555
|
CardRootProps,
|
|
480
556
|
CardHeaderProps,
|
|
557
|
+
CardBlockProps,
|
|
481
558
|
CardDragHandleProps,
|
|
482
559
|
CardActionIconButtonProps,
|
|
483
560
|
CardMenuProps,
|
|
@@ -9,20 +9,47 @@ import { withColumn } from './withColumn';
|
|
|
9
9
|
|
|
10
10
|
export type ColumnStyleProps = {};
|
|
11
11
|
|
|
12
|
+
export type ColumnBlockStyleProps = {
|
|
13
|
+
/** Trailing gutter (column 3) instead of the default leading gutter (column 1). */
|
|
14
|
+
end?: boolean;
|
|
15
|
+
compact?: boolean;
|
|
16
|
+
/** Constrain to a square (1:1) slot rather than the default rail-item width. */
|
|
17
|
+
square?: boolean;
|
|
18
|
+
};
|
|
19
|
+
|
|
12
20
|
const root: ComponentFunction<ColumnStyleProps> = (_, ...etc) => {
|
|
13
21
|
return mx('dx-column-root grid', ...etc);
|
|
14
22
|
};
|
|
15
23
|
|
|
16
24
|
/**
|
|
17
|
-
* Three-column
|
|
18
|
-
*
|
|
19
|
-
*
|
|
25
|
+
* Three-column subgrid row spanning all 3 columns of the parent Column.Root grid.
|
|
26
|
+
* Placement is explicit, not source-order based:
|
|
27
|
+
* - `Column.Block` self-places into column 1 (leading) or column 3 (trailing, `end`) and
|
|
28
|
+
* carries the `dx-gutter` marker class.
|
|
29
|
+
* - Every other (content) child is placed in column 2 via `[&>*:not(.dx-gutter)]`.
|
|
30
|
+
* Class-based placement (rather than `[data-slot=…]`) is used because Tailwind does not
|
|
31
|
+
* generate arbitrary variants that nest a square-bracket attribute selector.
|
|
32
|
+
* Robust to conditional children: a falsy leading slot never shifts content into a gutter.
|
|
20
33
|
* NOTE: Must not use overflow-hidden here since it will clip input focus rings.
|
|
21
34
|
*/
|
|
22
35
|
const row: ComponentFunction<ColumnStyleProps> = (_, ...etc) => {
|
|
23
|
-
return mx('col-span-3 grid grid-cols-subgrid', ...etc);
|
|
36
|
+
return mx('col-span-3 grid grid-cols-subgrid', '[&>*:not(.dx-gutter)]:col-start-2', ...etc);
|
|
24
37
|
};
|
|
25
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Gutter slot geometry: a `--dx-rail-item` square that centers its child, so a passive
|
|
41
|
+
* `<Icon>` and an interactive `IconButton` line up to the pixel. Self-places into column 1
|
|
42
|
+
* (default) or column 3 (`end`); the `dx-gutter` marker keeps it out of the content track.
|
|
43
|
+
*/
|
|
44
|
+
const block: ComponentFunction<ColumnBlockStyleProps> = ({ end, compact, square }, ...etc) =>
|
|
45
|
+
mx(
|
|
46
|
+
'dx-gutter grid place-items-center [&>img]:max-w-[1.5rem]',
|
|
47
|
+
end ? 'col-start-3' : 'col-start-1',
|
|
48
|
+
square ? 'aspect-square' : 'w-[var(--dx-rail-item)]',
|
|
49
|
+
compact ? '' : 'h-[var(--dx-rail-item)]',
|
|
50
|
+
...etc,
|
|
51
|
+
);
|
|
52
|
+
|
|
26
53
|
/**
|
|
27
54
|
* Bleed placement: spans all 3 columns of the parent Column.Root grid (gutter-to-gutter).
|
|
28
55
|
* Use for `ScrollArea`, full-width dividers, tables, or any content that should ignore gutters.
|
|
@@ -43,6 +70,7 @@ const center: ComponentFunction<ColumnStyleProps> = (_, ...etc) => {
|
|
|
43
70
|
export const columnTheme = {
|
|
44
71
|
root,
|
|
45
72
|
row,
|
|
73
|
+
block,
|
|
46
74
|
bleed,
|
|
47
75
|
center,
|
|
48
76
|
};
|
|
@@ -152,6 +152,40 @@ const ColumnCenter = slottable<HTMLDivElement>(({ children, asChild, ...props },
|
|
|
152
152
|
|
|
153
153
|
ColumnCenter.displayName = COLUMN_CENTER_NAME;
|
|
154
154
|
|
|
155
|
+
//
|
|
156
|
+
// Block
|
|
157
|
+
//
|
|
158
|
+
|
|
159
|
+
const COLUMN_BLOCK_NAME = 'Column.Block';
|
|
160
|
+
|
|
161
|
+
type ColumnBlockProps = SlottableProps<{ end?: boolean; compact?: boolean; square?: boolean }>;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* A gutter slot inside a Column.Row. Sized to `--dx-rail-item` and centers its child so a passive
|
|
165
|
+
* `<Icon>` and an interactive `IconButton` align to the pixel. `end` opts into the trailing gutter
|
|
166
|
+
* (column 3); default is the leading gutter (column 1). Placement is via `data-slot`, so it is
|
|
167
|
+
* robust to conditional rendering and source order.
|
|
168
|
+
*/
|
|
169
|
+
const ColumnBlock = slottable<HTMLDivElement, ColumnBlockProps>(
|
|
170
|
+
({ children, asChild, end, compact, square, ...props }, forwardedRef) => {
|
|
171
|
+
const { tx } = useThemeContext();
|
|
172
|
+
const { className, ...rest } = composableProps(props);
|
|
173
|
+
const Comp = asChild ? Slot : Primitive.div;
|
|
174
|
+
return (
|
|
175
|
+
<Comp
|
|
176
|
+
{...rest}
|
|
177
|
+
data-slot={end ? 'end' : 'start'}
|
|
178
|
+
className={tx('column.block', { end, compact, square }, className)}
|
|
179
|
+
ref={forwardedRef}
|
|
180
|
+
>
|
|
181
|
+
{children}
|
|
182
|
+
</Comp>
|
|
183
|
+
);
|
|
184
|
+
},
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
ColumnBlock.displayName = COLUMN_BLOCK_NAME;
|
|
188
|
+
|
|
155
189
|
//
|
|
156
190
|
// Column
|
|
157
191
|
//
|
|
@@ -159,8 +193,9 @@ ColumnCenter.displayName = COLUMN_CENTER_NAME;
|
|
|
159
193
|
export const Column = {
|
|
160
194
|
Root: ColumnRoot,
|
|
161
195
|
Row: ColumnRow,
|
|
196
|
+
Block: ColumnBlock,
|
|
162
197
|
Bleed: ColumnBleed,
|
|
163
198
|
Center: ColumnCenter,
|
|
164
199
|
};
|
|
165
200
|
|
|
166
|
-
export type { ColumnRootProps, ColumnRowProps, ColumnBleedProps, ColumnCenterProps };
|
|
201
|
+
export type { ColumnRootProps, ColumnRowProps, ColumnBlockProps, ColumnBleedProps, ColumnCenterProps };
|
|
@@ -22,14 +22,6 @@ const title: ComponentFunction<MessageStyleProps> = (_, etc) => {
|
|
|
22
22
|
return mx('col-start-2 overflow-hidden truncate', etc);
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
const icon: ComponentFunction<MessageStyleProps> = (_, etc) => {
|
|
26
|
-
return mx('col-start-1 grid place-items-center', etc);
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const close: ComponentFunction<MessageStyleProps> = (_, etc) => {
|
|
30
|
-
return mx('col-start-3', etc);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
25
|
const content: ComponentFunction<MessageStyleProps> = (_, etc) => {
|
|
34
26
|
return mx('col-start-2 flex flex-col first:font-medium pb-1.5', etc);
|
|
35
27
|
};
|
|
@@ -37,8 +29,6 @@ const content: ComponentFunction<MessageStyleProps> = (_, etc) => {
|
|
|
37
29
|
export const messageTheme: Theme<MessageStyleProps> = {
|
|
38
30
|
root,
|
|
39
31
|
header,
|
|
40
|
-
icon,
|
|
41
32
|
title,
|
|
42
|
-
close,
|
|
43
33
|
content,
|
|
44
34
|
};
|
|
@@ -141,15 +141,15 @@ const MessageTitle = forwardRef<HTMLDivElement, MessageTitleProps>(
|
|
|
141
141
|
return (
|
|
142
142
|
<Column.Row classNames={tx('message.header', {}, classNames)} ref={forwardedRef}>
|
|
143
143
|
{icon && (
|
|
144
|
-
<
|
|
144
|
+
<Column.Block>
|
|
145
145
|
<Icon icon={icon} />
|
|
146
|
-
</
|
|
146
|
+
</Column.Block>
|
|
147
147
|
)}
|
|
148
148
|
<h2 className={tx('message.title', {}, classNames)} id={titleId}>
|
|
149
149
|
{children}
|
|
150
150
|
</h2>
|
|
151
151
|
{onClose && (
|
|
152
|
-
<
|
|
152
|
+
<Column.Block end>
|
|
153
153
|
<IconButton
|
|
154
154
|
variant='ghost'
|
|
155
155
|
icon='ph--x--regular'
|
|
@@ -158,7 +158,7 @@ const MessageTitle = forwardRef<HTMLDivElement, MessageTitleProps>(
|
|
|
158
158
|
label={t('toolbar-close.label')}
|
|
159
159
|
onClick={onClose}
|
|
160
160
|
/>
|
|
161
|
-
</
|
|
161
|
+
</Column.Block>
|
|
162
162
|
)}
|
|
163
163
|
</Column.Row>
|
|
164
164
|
);
|
|
@@ -13,7 +13,7 @@ export type PopoverStyleProps = Partial<{
|
|
|
13
13
|
|
|
14
14
|
const content: ComponentFunction<PopoverStyleProps> = ({ elevation }, ...etc) =>
|
|
15
15
|
mx(
|
|
16
|
-
'dx-popover-surface border border-separator rounded-sm',
|
|
16
|
+
'dx-popover-surface overflow-hidden border border-separator rounded-sm',
|
|
17
17
|
surfaceShadow({ elevation: 'positioned' }),
|
|
18
18
|
surfaceZIndex({ elevation, level: 'menu' }),
|
|
19
19
|
'dx-focus-ring',
|
|
@@ -32,12 +32,8 @@ const grid: ComponentFunction<ToastStyleProps> = (_props, ...etc) => mx('gap-y-1
|
|
|
32
32
|
|
|
33
33
|
const header: ComponentFunction<ToastStyleProps> = (_props, ...etc) => mx('items-center', ...etc);
|
|
34
34
|
|
|
35
|
-
const icon: ComponentFunction<ToastStyleProps> = (_props, ...etc) => mx('col-start-1 grid place-items-center', ...etc);
|
|
36
|
-
|
|
37
35
|
const title: ComponentFunction<ToastStyleProps> = (_props, ...etc) => mx('col-start-2 truncate font-medium', ...etc);
|
|
38
36
|
|
|
39
|
-
const close: ComponentFunction<ToastStyleProps> = (_props, ...etc) => mx('col-start-3', ...etc);
|
|
40
|
-
|
|
41
37
|
const description: ComponentFunction<ToastStyleProps> = (_props, ...etc) =>
|
|
42
38
|
mx('col-start-2 overflow-hidden text-description', ...etc);
|
|
43
39
|
|
|
@@ -48,9 +44,7 @@ export const toastTheme: Theme<ToastStyleProps> = {
|
|
|
48
44
|
root,
|
|
49
45
|
grid,
|
|
50
46
|
header,
|
|
51
|
-
icon,
|
|
52
47
|
title,
|
|
53
|
-
close,
|
|
54
48
|
description,
|
|
55
49
|
actions,
|
|
56
50
|
};
|
|
@@ -71,22 +71,23 @@ const ToastTitle = forwardRef<HTMLHeadingElement, ToastTitleProps>(
|
|
|
71
71
|
return (
|
|
72
72
|
<Column.Row classNames={tx('toast.header', {})}>
|
|
73
73
|
{icon && (
|
|
74
|
-
<
|
|
74
|
+
<Column.Block>
|
|
75
75
|
<Icon icon={icon} size={5} />
|
|
76
|
-
</
|
|
76
|
+
</Column.Block>
|
|
77
77
|
)}
|
|
78
78
|
<ToastPrimitive.Title {...props} className={tx('toast.title', {}, classNames)} ref={forwardedRef}>
|
|
79
79
|
{children}
|
|
80
80
|
</ToastPrimitive.Title>
|
|
81
81
|
{onClose && (
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
82
|
+
<Column.Block end>
|
|
83
|
+
<IconButton
|
|
84
|
+
variant='ghost'
|
|
85
|
+
icon='ph--x--regular'
|
|
86
|
+
iconOnly
|
|
87
|
+
label={t('toolbar-close.label')}
|
|
88
|
+
onClick={onClose}
|
|
89
|
+
/>
|
|
90
|
+
</Column.Block>
|
|
90
91
|
)}
|
|
91
92
|
</Column.Row>
|
|
92
93
|
);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useComposedRefs } from '@radix-ui/react-compose-refs';
|
|
6
|
+
import React, {
|
|
7
|
+
type ComponentPropsWithoutRef,
|
|
8
|
+
type PropsWithChildren,
|
|
9
|
+
type SyntheticEvent,
|
|
10
|
+
forwardRef,
|
|
11
|
+
useCallback,
|
|
12
|
+
useRef,
|
|
13
|
+
} from 'react';
|
|
14
|
+
|
|
15
|
+
import { Tooltip, type TooltipScopedProps, type TooltipTriggerProps } from './Tooltip';
|
|
16
|
+
|
|
17
|
+
export type TextTooltipProps = PropsWithChildren<
|
|
18
|
+
{
|
|
19
|
+
text: string;
|
|
20
|
+
asChild?: boolean;
|
|
21
|
+
onlyWhenTruncating?: boolean;
|
|
22
|
+
truncateQuery?: string;
|
|
23
|
+
} & Pick<TooltipTriggerProps, 'side'> &
|
|
24
|
+
ComponentPropsWithoutRef<'button'>
|
|
25
|
+
>;
|
|
26
|
+
|
|
27
|
+
export const TextTooltip = forwardRef<HTMLButtonElement, TooltipScopedProps<TextTooltipProps>>(
|
|
28
|
+
(
|
|
29
|
+
{ __scopeTooltip, text, children, onlyWhenTruncating, asChild = true, side, truncateQuery, ...props },
|
|
30
|
+
forwardedRef,
|
|
31
|
+
) => {
|
|
32
|
+
const content = useRef<HTMLButtonElement | null>(null);
|
|
33
|
+
const ref = useComposedRefs(content, forwardedRef);
|
|
34
|
+
const handleInteract = useCallback(
|
|
35
|
+
(event: SyntheticEvent) => {
|
|
36
|
+
if (onlyWhenTruncating && content.current) {
|
|
37
|
+
const element: HTMLElement | null = truncateQuery
|
|
38
|
+
? content.current.querySelector(truncateQuery)
|
|
39
|
+
: content.current;
|
|
40
|
+
if (!element || element.scrollWidth <= element.offsetWidth) {
|
|
41
|
+
event.preventDefault();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
[onlyWhenTruncating, truncateQuery],
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Tooltip.Trigger asChild={asChild} {...props} content={text} side={side} onInteract={handleInteract} ref={ref}>
|
|
50
|
+
{children}
|
|
51
|
+
</Tooltip.Trigger>
|
|
52
|
+
);
|
|
53
|
+
},
|
|
54
|
+
);
|