@dxos/ui-theme 0.8.4-main.6fa680abb7 → 0.8.4-main.74a063c4e0
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/index.mjs +355 -443
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +355 -443
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/plugin/node-cjs/{theme.css → main.css} +66 -31
- package/dist/plugin/node-cjs/main.css.map +7 -0
- package/dist/plugin/node-cjs/meta.json +1 -1
- package/dist/plugin/node-cjs/plugins/ThemePlugin.cjs +8 -15
- package/dist/plugin/node-cjs/plugins/ThemePlugin.cjs.map +3 -3
- package/dist/plugin/node-esm/{theme.css → main.css} +66 -31
- package/dist/plugin/node-esm/main.css.map +7 -0
- package/dist/plugin/node-esm/meta.json +1 -1
- package/dist/plugin/node-esm/plugins/ThemePlugin.mjs +8 -15
- package/dist/plugin/node-esm/plugins/ThemePlugin.mjs.map +3 -3
- package/dist/types/src/fragments/index.d.ts +0 -5
- package/dist/types/src/fragments/index.d.ts.map +1 -1
- package/dist/types/src/fragments/text.d.ts +0 -4
- package/dist/types/src/fragments/text.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/plugins/ThemePlugin.d.ts.map +1 -1
- package/dist/types/src/theme/components/avatar.d.ts.map +1 -1
- package/dist/types/src/theme/components/card.d.ts +3 -2
- package/dist/types/src/theme/components/card.d.ts.map +1 -1
- package/dist/types/src/theme/components/dialog.d.ts.map +1 -1
- package/dist/types/src/theme/components/focus.d.ts +6 -0
- package/dist/types/src/theme/components/focus.d.ts.map +1 -0
- package/dist/types/src/theme/components/icon-button.d.ts.map +1 -1
- package/dist/types/src/theme/components/icon.d.ts +3 -0
- package/dist/types/src/theme/components/icon.d.ts.map +1 -1
- package/dist/types/src/theme/components/index.d.ts +1 -0
- package/dist/types/src/theme/components/index.d.ts.map +1 -1
- package/dist/types/src/theme/components/input.d.ts.map +1 -1
- package/dist/types/src/theme/components/link.d.ts.map +1 -1
- package/dist/types/src/theme/components/list.d.ts.map +1 -1
- package/dist/types/src/theme/components/message.d.ts.map +1 -1
- package/dist/types/src/theme/components/popover.d.ts.map +1 -1
- package/dist/types/src/theme/components/scroll-area.d.ts +12 -2
- package/dist/types/src/theme/components/scroll-area.d.ts.map +1 -1
- package/dist/types/src/theme/components/select.d.ts.map +1 -1
- package/dist/types/src/theme/components/status.d.ts +1 -1
- package/dist/types/src/theme/components/status.d.ts.map +1 -1
- package/dist/types/src/theme/components/toast.d.ts.map +1 -1
- package/dist/types/src/theme/components/toolbar.d.ts +0 -1
- package/dist/types/src/theme/components/toolbar.d.ts.map +1 -1
- package/dist/types/src/theme/components/tooltip.d.ts.map +1 -1
- package/dist/types/src/theme/components/treegrid.d.ts.map +1 -1
- package/dist/types/src/theme/index.d.ts +1 -0
- package/dist/types/src/theme/index.d.ts.map +1 -1
- package/dist/types/src/theme/primitives/column.d.ts +8 -3
- package/dist/types/src/theme/primitives/column.d.ts.map +1 -1
- package/dist/types/src/theme/primitives/panel.d.ts +9 -5
- package/dist/types/src/theme/primitives/panel.d.ts.map +1 -1
- package/dist/types/src/theme/theme.d.ts.map +1 -1
- package/dist/types/src/util/elevation.d.ts.map +1 -0
- package/dist/types/src/util/index.d.ts +3 -0
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/mx.d.ts +28 -284
- package/dist/types/src/util/mx.d.ts.map +1 -1
- package/dist/types/src/util/size.d.ts +27 -0
- package/dist/types/src/util/size.d.ts.map +1 -0
- package/dist/types/src/util/valence.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -4
- package/src/css/components/button.css +2 -1
- package/src/css/components/{focus-ring.css → focus.css} +15 -1
- package/src/css/components/icon.css +9 -0
- package/src/css/components/selected.css +30 -0
- package/src/css/integrations/codemirror.css +5 -3
- package/src/css/integrations/tldraw.css +1 -0
- package/src/css/layout/main.css +0 -7
- package/src/css/layout/size.css +8 -31
- package/src/css/theme/palette.css +8 -0
- package/src/css/theme/semantic.css +24 -9
- package/src/css/theme/spacing.css +35 -18
- package/src/css/utilities.css +72 -3
- package/src/fragments/AUDIT.md +58 -0
- package/src/fragments/index.ts +1 -6
- package/src/fragments/text.ts +0 -5
- package/src/index.ts +1 -1
- package/src/{theme.css → main.css} +10 -6
- package/src/plugins/ThemePlugin.ts +12 -24
- package/src/plugins/main.css +45 -0
- package/src/theme/components/avatar.ts +3 -4
- package/src/theme/components/button.ts +1 -1
- package/src/theme/components/card.ts +14 -10
- package/src/theme/components/dialog.ts +1 -2
- package/src/theme/components/focus.ts +33 -0
- package/src/theme/components/icon-button.ts +1 -3
- package/src/theme/components/icon.ts +13 -4
- package/src/theme/components/index.ts +1 -0
- package/src/theme/components/input.ts +10 -25
- package/src/theme/components/link.ts +1 -2
- package/src/theme/components/list.ts +4 -4
- package/src/theme/components/menu.ts +4 -4
- package/src/theme/components/message.ts +2 -3
- package/src/theme/components/popover.ts +3 -4
- package/src/theme/components/scroll-area.ts +47 -40
- package/src/theme/components/select.ts +2 -3
- package/src/theme/components/status.ts +5 -5
- package/src/theme/components/toast.ts +2 -3
- package/src/theme/components/toolbar.ts +1 -7
- package/src/theme/components/tooltip.ts +1 -2
- package/src/theme/components/treegrid.ts +1 -1
- package/src/theme/index.ts +1 -0
- package/src/theme/primitives/column.ts +27 -10
- package/src/theme/primitives/panel.ts +18 -25
- package/src/theme/theme.ts +2 -0
- package/src/typings.d.ts +3 -0
- package/src/util/index.ts +3 -0
- package/src/util/mx.ts +106 -7
- package/src/util/size.ts +103 -0
- package/dist/plugin/node-cjs/theme.css.map +0 -7
- package/dist/plugin/node-esm/theme.css.map +0 -7
- package/dist/types/src/fragments/elevation.d.ts.map +0 -1
- package/dist/types/src/fragments/focus.d.ts +0 -4
- package/dist/types/src/fragments/focus.d.ts.map +0 -1
- package/dist/types/src/fragments/selected.d.ts +0 -4
- package/dist/types/src/fragments/selected.d.ts.map +0 -1
- package/dist/types/src/fragments/size.d.ts +0 -7
- package/dist/types/src/fragments/size.d.ts.map +0 -1
- package/dist/types/src/fragments/valence.d.ts.map +0 -1
- package/src/fragments/focus.ts +0 -11
- package/src/fragments/selected.ts +0 -12
- package/src/fragments/size.ts +0 -117
- /package/dist/types/src/{fragments → util}/elevation.d.ts +0 -0
- /package/dist/types/src/{fragments → util}/valence.d.ts +0 -0
- /package/src/{fragments → util}/elevation.ts +0 -0
- /package/src/{fragments → util}/valence.ts +0 -0
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { type ComponentFunction, type Elevation, type Theme } from '@dxos/ui-types';
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import { mx } from '../../util';
|
|
7
|
+
import { mx, surfaceShadow, surfaceZIndex } from '../../util';
|
|
9
8
|
|
|
10
9
|
export type SelectStyleProps = Partial<{
|
|
11
10
|
elevation: Elevation;
|
|
@@ -28,7 +27,7 @@ export const selectItem: ComponentFunction<SelectStyleProps> = (_props, ...etc)
|
|
|
28
27
|
'flex items-center min-h-[2rem] px-3 py-1 gap-2',
|
|
29
28
|
'text-base-surface-text leading-none select-none outline-hidden',
|
|
30
29
|
'[&>svg]:invisible [&[data-state=checked]>svg]:visible',
|
|
31
|
-
|
|
30
|
+
'dx-highlighted',
|
|
32
31
|
...etc,
|
|
33
32
|
);
|
|
34
33
|
|
|
@@ -7,21 +7,21 @@ import type { ComponentFunction, Theme } from '@dxos/ui-types';
|
|
|
7
7
|
import { mx } from '../../util';
|
|
8
8
|
|
|
9
9
|
export type StatusStyleProps = {
|
|
10
|
-
indeterminate?: boolean;
|
|
11
10
|
variant?: 'default' | 'main-bottom';
|
|
11
|
+
indeterminate?: boolean;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
export const statusRoot: ComponentFunction<StatusStyleProps> = ({ variant = 'default' }, ...etc) =>
|
|
15
15
|
mx(
|
|
16
|
-
'h-1 relative
|
|
17
|
-
variant === 'main-bottom' ? 'w-full block' : 'inline-20 inline-block',
|
|
16
|
+
'h-1 relative rounded-full overflow-hidden',
|
|
17
|
+
variant === 'main-bottom' ? 'w-full block' : 'inline-20 inline-block bg-base-surface',
|
|
18
18
|
...etc,
|
|
19
19
|
);
|
|
20
20
|
|
|
21
|
-
export const statusBar: ComponentFunction<StatusStyleProps> = ({
|
|
21
|
+
export const statusBar: ComponentFunction<StatusStyleProps> = ({ variant = 'default', indeterminate }, ...etc) =>
|
|
22
22
|
mx(
|
|
23
23
|
'absolute inset-y-0 block rounded-full',
|
|
24
|
-
variant === 'main-bottom' ? 'bg-
|
|
24
|
+
variant === 'main-bottom' ? 'bg-composer-300' : 'bg-un-accent',
|
|
25
25
|
indeterminate ? 'animate-progress-indeterminate' : 'start-0',
|
|
26
26
|
...etc,
|
|
27
27
|
);
|
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { type ComponentFunction, type Theme } from '@dxos/ui-types';
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import { mx } from '../../util';
|
|
7
|
+
import { mx, surfaceShadow } from '../../util';
|
|
9
8
|
|
|
10
9
|
export type ToastStyleProps = Partial<{
|
|
11
10
|
srOnly: boolean;
|
|
@@ -28,7 +27,7 @@ export const toastRoot: ComponentFunction<ToastStyleProps> = (_props, ...etc) =>
|
|
|
28
27
|
'radix-swipe-end:animate-toast-swipe-out',
|
|
29
28
|
'translate-x-radix-toast-swipe-move-x',
|
|
30
29
|
'radix-swipe-cancel:translate-x-0 radix-swipe-cancel:duration-200 radix-swipe-cancel:ease-[ease]',
|
|
31
|
-
|
|
30
|
+
'dx-focus-ring',
|
|
32
31
|
...etc,
|
|
33
32
|
);
|
|
34
33
|
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
import { type ComponentFunction, type Density, type Theme } from '@dxos/ui-types';
|
|
6
6
|
|
|
7
|
-
import { textBlockWidth } from '../../fragments';
|
|
8
7
|
import { mx } from '../../util';
|
|
9
8
|
|
|
10
9
|
export type ToolbarStyleProps = Partial<{
|
|
@@ -26,16 +25,11 @@ export const toolbarRoot: ComponentFunction<ToolbarStyleProps> = ({ density, dis
|
|
|
26
25
|
);
|
|
27
26
|
};
|
|
28
27
|
|
|
29
|
-
export const toolbarInner: ComponentFunction<ToolbarStyleProps> = ({ layoutManaged }, ...etc) => {
|
|
30
|
-
return mx(!layoutManaged && ['flex gap-1', textBlockWidth], ...etc);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
28
|
export const toolbarText: ComponentFunction<ToolbarStyleProps> = (_, ...etc) => {
|
|
34
|
-
return mx('grow truncate items-center',
|
|
29
|
+
return mx('px-2 grow truncate items-center', ...etc);
|
|
35
30
|
};
|
|
36
31
|
|
|
37
32
|
export const toolbarTheme: Theme<ToolbarStyleProps> = {
|
|
38
33
|
root: toolbarRoot,
|
|
39
|
-
inner: toolbarInner,
|
|
40
34
|
text: toolbarText,
|
|
41
35
|
};
|
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { type ComponentFunction, type Elevation, type Theme } from '@dxos/ui-types';
|
|
6
6
|
|
|
7
|
-
import { surfaceShadow, surfaceZIndex } from '../../
|
|
8
|
-
import { mx } from '../../util';
|
|
7
|
+
import { mx, surfaceShadow, surfaceZIndex } from '../../util';
|
|
9
8
|
|
|
10
9
|
export type TooltipStyleProps = Partial<{
|
|
11
10
|
elevation: Elevation;
|
|
@@ -25,7 +25,7 @@ const levelStyles = new Map<number, string>([
|
|
|
25
25
|
export const treegridRoot: ComponentFunction<TreegridStyleProps> = (_, ...etc) => mx('grid', ...etc);
|
|
26
26
|
|
|
27
27
|
export const treegridRow: ComponentFunction<TreegridStyleProps> = ({ level = 1 }, ...etc) =>
|
|
28
|
-
mx(
|
|
28
|
+
mx(levelStyles.get(Math.min(Math.max(Math.round(level), 1), 8)), ...etc);
|
|
29
29
|
|
|
30
30
|
export const treegridCell: ComponentFunction<TreegridStyleProps> = ({ indent }, ...etc) =>
|
|
31
31
|
mx(indent && 'indent', ...etc);
|
package/src/theme/index.ts
CHANGED
|
@@ -6,25 +6,42 @@ import { type ComponentFunction } from '@dxos/ui-types';
|
|
|
6
6
|
|
|
7
7
|
import { mx } from '../../util';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
export type ColumnStyleProps = {
|
|
10
|
+
fullWidth?: boolean;
|
|
11
|
+
center?: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const columnRoot: ComponentFunction<ColumnStyleProps> = (_, ...etc) => {
|
|
15
|
+
return mx('dx-column grid', ...etc);
|
|
16
|
+
};
|
|
10
17
|
|
|
11
18
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
19
|
+
* Full-width content area that inherits the parent Column.Root's 3-column grid via subgrid.
|
|
20
|
+
* Non-scrolling children default to column 2 (center, between gutters).
|
|
21
|
+
* ScrollArea children span all 3 columns via the existing `[.dx-column_&]:col-span-full` selector.
|
|
22
|
+
* This avoids padding/overflow conflicts — gutters come from the grid, not padding.
|
|
15
23
|
*/
|
|
16
|
-
const
|
|
17
|
-
mx('col-span-
|
|
24
|
+
const columnContent: ComponentFunction<ColumnStyleProps> = (_, ...etc) => {
|
|
25
|
+
return mx('col-span-full grid grid-cols-subgrid min-h-0 [&>:not(.dx-container)]:col-start-2', ...etc);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const columnViewport: ComponentFunction<ColumnStyleProps> = (_, ...etc) => {
|
|
29
|
+
return mx(...etc);
|
|
30
|
+
};
|
|
18
31
|
|
|
19
32
|
/**
|
|
33
|
+
* Three-column icon-slot row: spans all 3 columns of the parent Column.Root grid.
|
|
34
|
+
* Uses CSS subgrid to inherit column sizing from the parent Column.
|
|
35
|
+
* Children map to: [col-1: icon/slot] [col-2: content] [col-3: icon/action].
|
|
20
36
|
* NOTE: Must not use overflow-hidden here since it will clip input focus rings.
|
|
21
|
-
* Occupies only the center column (col-2) of the parent Column.Root grid.
|
|
22
37
|
*/
|
|
23
|
-
const
|
|
24
|
-
mx('col-start-2 col-span-1
|
|
38
|
+
const columnRow: ComponentFunction<ColumnStyleProps> = ({ fullWidth, center }, ...etc) => {
|
|
39
|
+
return mx('col-span-3 grid grid-cols-subgrid', fullWidth ? 'col-span-3' : center && 'col-start-2 col-span-1', ...etc);
|
|
40
|
+
};
|
|
25
41
|
|
|
26
42
|
export const columnTheme = {
|
|
27
43
|
root: columnRoot,
|
|
44
|
+
content: columnContent,
|
|
45
|
+
viewport: columnViewport,
|
|
28
46
|
row: columnRow,
|
|
29
|
-
segment: columnSegment,
|
|
30
47
|
};
|
|
@@ -6,41 +6,34 @@ import { type ComponentFunction } from '@dxos/ui-types';
|
|
|
6
6
|
|
|
7
7
|
import { mx } from '../../util';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
type Size = 'lg' | 'md' | 'sm';
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
export type PanelStyleProps = {
|
|
12
|
+
size?: Size;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const sizes: Record<Size, string> = {
|
|
16
|
+
lg: 'h-(--dx-topbar-size)',
|
|
17
|
+
md: 'h-(--dx-toolbar-size)',
|
|
18
|
+
sm: 'h-(--dx-statusbar-size)',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const panelRoot: ComponentFunction<PanelStyleProps> = (_, ...etc) =>
|
|
12
22
|
mx(
|
|
13
23
|
// prettier-ignore
|
|
14
|
-
'
|
|
24
|
+
'dx-container grid grid-cols-[100%] overflow-hidden',
|
|
15
25
|
// Add uncategorized children to content slot.
|
|
16
26
|
'[&>*:not([data-slot])]:[grid-area:content]',
|
|
17
27
|
...etc,
|
|
18
28
|
);
|
|
19
29
|
|
|
20
|
-
|
|
30
|
+
const panelToolbar: ComponentFunction<PanelStyleProps> = ({ size = 'md' }, ...etc) =>
|
|
31
|
+
mx('[grid-area:toolbar]', 'shrink-0', sizes[size], ...etc);
|
|
21
32
|
|
|
22
|
-
const
|
|
23
|
-
mx(
|
|
24
|
-
// prettier-ignore
|
|
25
|
-
'[grid-area:toolbar]',
|
|
26
|
-
'border-b border-subdued-separator relative',
|
|
27
|
-
'[.dx-main-mobile-layout_&]:px-3',
|
|
28
|
-
...etc,
|
|
29
|
-
);
|
|
33
|
+
const panelContent: ComponentFunction<PanelStyleProps> = (_, ...etc) => mx('[grid-area:content] min-h-0', ...etc);
|
|
30
34
|
|
|
31
|
-
const
|
|
32
|
-
mx(
|
|
33
|
-
// prettier-ignore
|
|
34
|
-
'[grid-area:content] overflow-hidden min-h-0',
|
|
35
|
-
...etc,
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
const panelStatusbar: ComponentFunction<PanelProps> = (_, ...etc) =>
|
|
39
|
-
mx(
|
|
40
|
-
// prettier-ignore
|
|
41
|
-
'[grid-area:statusbar]',
|
|
42
|
-
...etc,
|
|
43
|
-
);
|
|
35
|
+
const panelStatusbar: ComponentFunction<PanelStyleProps> = ({ size = 'md' }, ...etc) =>
|
|
36
|
+
mx('[grid-area:statusbar]', 'shrink-0', sizes[size], ...etc);
|
|
44
37
|
|
|
45
38
|
export const panelTheme = {
|
|
46
39
|
root: panelRoot,
|
package/src/theme/theme.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
buttonTheme,
|
|
12
12
|
cardTheme,
|
|
13
13
|
dialogTheme,
|
|
14
|
+
focusTheme,
|
|
14
15
|
iconButtonTheme,
|
|
15
16
|
iconTheme,
|
|
16
17
|
inputTheme,
|
|
@@ -53,6 +54,7 @@ export const defaultTheme: Theme<Record<string, any>> = {
|
|
|
53
54
|
button: buttonTheme,
|
|
54
55
|
card: cardTheme,
|
|
55
56
|
dialog: dialogTheme,
|
|
57
|
+
focus: focusTheme,
|
|
56
58
|
icon: iconTheme,
|
|
57
59
|
iconButton: iconButtonTheme,
|
|
58
60
|
input: inputTheme,
|
package/src/typings.d.ts
CHANGED
package/src/util/index.ts
CHANGED
package/src/util/mx.ts
CHANGED
|
@@ -2,12 +2,21 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import {
|
|
6
|
+
Children,
|
|
7
|
+
createElement,
|
|
8
|
+
forwardRef,
|
|
9
|
+
type ForwardRefExoticComponent,
|
|
10
|
+
type ForwardedRef,
|
|
11
|
+
type HTMLAttributes,
|
|
12
|
+
isValidElement,
|
|
13
|
+
type ReactNode,
|
|
14
|
+
type RefAttributes,
|
|
15
|
+
} from 'react';
|
|
16
|
+
import { extendTailwindMerge, validators } from 'tailwind-merge';
|
|
7
17
|
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
import { type ComposableProps } from '@dxos/ui-types';
|
|
18
|
+
import { log } from '@dxos/log';
|
|
19
|
+
import { ThemedClassName, type ComposableProps, type SlottableProps } from '@dxos/ui-types';
|
|
11
20
|
|
|
12
21
|
type AdditionalClassGroups = 'density' | 'dx-focus-ring';
|
|
13
22
|
|
|
@@ -58,11 +67,101 @@ export const mx = extendTailwindMerge<AdditionalClassGroups>({
|
|
|
58
67
|
* - `classNames` is the consumer-facing prop for theming overrides.
|
|
59
68
|
* Use `composableProps` to reconcile both into a single `className`.
|
|
60
69
|
*/
|
|
70
|
+
// TODO(burdon): Move to react-ui.
|
|
61
71
|
export const composableProps = <P extends HTMLElement = HTMLElement>(
|
|
62
|
-
{ className, classNames, ...props }: ComposableProps,
|
|
63
|
-
{
|
|
72
|
+
{ className, classNames, role, ...props }: ComposableProps,
|
|
73
|
+
{ classNames: defaultClassNames, ...defaults }: ThemedClassName<Partial<HTMLAttributes<P>>> | undefined = {},
|
|
64
74
|
) => ({
|
|
75
|
+
// Default props.
|
|
65
76
|
...(defaults as object),
|
|
77
|
+
|
|
78
|
+
// Spread supplied props; prefer explicit role, then defaults role, then 'none'.
|
|
79
|
+
role: (role ?? (defaults as Record<string, string>).role ?? 'none') as string,
|
|
66
80
|
...props,
|
|
81
|
+
|
|
82
|
+
// Compose classnames.
|
|
67
83
|
className: mx(defaultClassNames, className, classNames),
|
|
68
84
|
});
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Factory for slottable components.
|
|
88
|
+
* The implementation receives full `HTMLAttributes<E>` so it can destructure `role`, `style`, etc.
|
|
89
|
+
* Consumers see only `SlottableProps<P>` — a narrow type exposing `classNames`, `className`,
|
|
90
|
+
* `children`, `asChild`, and the custom props `P`.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```tsx
|
|
94
|
+
* const MyPanel = slottable<HTMLDivElement, { border?: boolean }>(
|
|
95
|
+
* ({ children, asChild, border, ...props }, forwardedRef) => {
|
|
96
|
+
* const Comp = asChild ? Slot : Primitive.div;
|
|
97
|
+
* return (
|
|
98
|
+
* <Comp {...composableProps(props, { classNames: border && 'border' })} ref={forwardedRef}>
|
|
99
|
+
* {children}
|
|
100
|
+
* </Comp>
|
|
101
|
+
* );
|
|
102
|
+
* },
|
|
103
|
+
* );
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
/** Symbol used to mark components created by `composable()` or `slottable()`. */
|
|
107
|
+
const COMPOSABLE = Symbol.for('dxos.composable');
|
|
108
|
+
|
|
109
|
+
export function slottable<E extends HTMLElement, P extends object = {}>(
|
|
110
|
+
render: (props: SlottableProps<P> & HTMLAttributes<E>, forwardedRef: ForwardedRef<E>) => ReactNode,
|
|
111
|
+
): ForwardRefExoticComponent<SlottableProps<P> & RefAttributes<E>> {
|
|
112
|
+
const wrapped = (props: SlottableProps<P> & HTMLAttributes<E>, forwardedRef: ForwardedRef<E>) => {
|
|
113
|
+
let warn = false;
|
|
114
|
+
if (props.asChild) {
|
|
115
|
+
try {
|
|
116
|
+
const child = Children.only(props.children);
|
|
117
|
+
if (isValidElement(child) && typeof child.type !== 'string' && !(child.type as any)[COMPOSABLE]) {
|
|
118
|
+
warn = true;
|
|
119
|
+
log.warn('slot child is not composable; create it with composable() or slottable()', {
|
|
120
|
+
child: (child.type as any).displayName ?? (child.type as any).name,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
} catch {
|
|
124
|
+
// Children.only throws if not exactly one child — Slot handles this.
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const result = render(props, forwardedRef);
|
|
129
|
+
if (warn) {
|
|
130
|
+
return createElement('div', { role: 'none', className: 'dx-slot-warning' }, result);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return result;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const component = forwardRef(wrapped as any) as any;
|
|
137
|
+
(component as any)[COMPOSABLE] = true;
|
|
138
|
+
return component;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Factory for composable (leaf) components.
|
|
143
|
+
* The implementation receives full `HTMLAttributes<E>` so it can destructure `role`, `style`, etc.
|
|
144
|
+
* Consumers see only `ComposableProps<P>` — a narrow type exposing `classNames`, `className`,
|
|
145
|
+
* `children`, and the custom props `P`.
|
|
146
|
+
*
|
|
147
|
+
* For generic components, use `any` for the type parameter inside `composable` and
|
|
148
|
+
* cast the result to restore the generic signature for consumers.
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```tsx
|
|
152
|
+
* const Leaf = composable<HTMLButtonElement>(({ children, ...props }, forwardedRef) => {
|
|
153
|
+
* return (
|
|
154
|
+
* <button {...composableProps(props, { classNames: 'btn' })} ref={forwardedRef}>
|
|
155
|
+
* {children}
|
|
156
|
+
* </button>
|
|
157
|
+
* );
|
|
158
|
+
* });
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
export function composable<E extends HTMLElement, P extends object = {}>(
|
|
162
|
+
render: (props: ComposableProps<P> & HTMLAttributes<E>, forwardedRef: ForwardedRef<E>) => ReactNode,
|
|
163
|
+
): ForwardRefExoticComponent<ComposableProps<P> & RefAttributes<E>> {
|
|
164
|
+
const component = forwardRef(render as any) as any;
|
|
165
|
+
(component as any)[COMPOSABLE] = true;
|
|
166
|
+
return component;
|
|
167
|
+
}
|
package/src/util/size.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2022 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type CSSProperties } from 'react';
|
|
6
|
+
|
|
7
|
+
import { type Size } from '@dxos/ui-types';
|
|
8
|
+
|
|
9
|
+
import { mx } from '../util';
|
|
10
|
+
|
|
11
|
+
// NOTE: Class names must be fully-specified string literals so Tailwind's static scanner can detect them.
|
|
12
|
+
const sizeMap: Record<string, { w: string; h: string }> = {
|
|
13
|
+
0: { w: 'w-0', h: 'h-0' },
|
|
14
|
+
px: { w: 'w-px', h: 'h-px' },
|
|
15
|
+
0.5: { w: 'w-0.5', h: 'h-0.5' },
|
|
16
|
+
1: { w: 'w-1', h: 'h-1' },
|
|
17
|
+
1.5: { w: 'w-1.5', h: 'h-1.5' },
|
|
18
|
+
2: { w: 'w-2', h: 'h-2' },
|
|
19
|
+
2.5: { w: 'w-2.5', h: 'h-2.5' },
|
|
20
|
+
3: { w: 'w-3', h: 'h-3' },
|
|
21
|
+
3.5: { w: 'w-3.5', h: 'h-3.5' },
|
|
22
|
+
4: { w: 'w-4', h: 'h-4' },
|
|
23
|
+
5: { w: 'w-5', h: 'h-5' },
|
|
24
|
+
6: { w: 'w-6', h: 'h-6' },
|
|
25
|
+
7: { w: 'w-7', h: 'h-7' },
|
|
26
|
+
8: { w: 'w-8', h: 'h-8' },
|
|
27
|
+
9: { w: 'w-9', h: 'h-9' },
|
|
28
|
+
10: { w: 'w-10', h: 'h-10' },
|
|
29
|
+
11: { w: 'w-11', h: 'h-11' },
|
|
30
|
+
12: { w: 'w-12', h: 'h-12' },
|
|
31
|
+
14: { w: 'w-14', h: 'h-14' },
|
|
32
|
+
16: { w: 'w-16', h: 'h-16' },
|
|
33
|
+
20: { w: 'w-20', h: 'h-20' },
|
|
34
|
+
24: { w: 'w-24', h: 'h-24' },
|
|
35
|
+
28: { w: 'w-28', h: 'h-28' },
|
|
36
|
+
32: { w: 'w-32', h: 'h-32' },
|
|
37
|
+
36: { w: 'w-36', h: 'h-36' },
|
|
38
|
+
40: { w: 'w-40', h: 'h-40' },
|
|
39
|
+
44: { w: 'w-44', h: 'h-44' },
|
|
40
|
+
48: { w: 'w-48', h: 'h-48' },
|
|
41
|
+
52: { w: 'w-52', h: 'h-52' },
|
|
42
|
+
56: { w: 'w-56', h: 'h-56' },
|
|
43
|
+
60: { w: 'w-60', h: 'h-60' },
|
|
44
|
+
64: { w: 'w-64', h: 'h-64' },
|
|
45
|
+
72: { w: 'w-72', h: 'h-72' },
|
|
46
|
+
80: { w: 'w-80', h: 'h-80' },
|
|
47
|
+
96: { w: 'w-96', h: 'h-96' },
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const SIZE_VALUES: Size[] = Object.keys(sizeMap).map((key) => (key === 'px' ? 'px' : (Number(key) as Size)));
|
|
51
|
+
|
|
52
|
+
export const getHeight = (size: Size) => sizeMap[size]?.h;
|
|
53
|
+
export const getWidth = (size: Size) => sizeMap[size]?.w;
|
|
54
|
+
export const getSize = (size: Size) => mx(getHeight(size), getWidth(size));
|
|
55
|
+
|
|
56
|
+
export const sizeValue = (size: Size): number => (size === 'px' ? 1 : size);
|
|
57
|
+
export const sizeToRem = (size: Size): string => (size === 'px' ? '1px' : `${(size as number) * 0.25}rem`);
|
|
58
|
+
|
|
59
|
+
export const iconSize = (size: Size | null): CSSProperties =>
|
|
60
|
+
({ '--icon-size': size ? sizeToRem(size) : 'initial' }) as CSSProperties;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Snaps an arbitrary numeric value to the nearest valid Tailwind size token.
|
|
64
|
+
*
|
|
65
|
+
* The function tries progressively coarser series in order until a match is found:
|
|
66
|
+
* 1. Exact match — returns the value as-is if it is already a valid `Size`.
|
|
67
|
+
* 2. Zero or negative — clamps to `0`.
|
|
68
|
+
* 3. Sub-half values (`< 0.5`) — maps to the `'px'` token (1 px).
|
|
69
|
+
* 4. Half-step series (0.5 increments, e.g. 1.5, 2.5 …).
|
|
70
|
+
* 5. Whole-number series (1, 2, 3 …).
|
|
71
|
+
* 6. Double series (even numbers: 2, 4, 6 …).
|
|
72
|
+
* 7. Quadruple series (multiples of 4: 4, 8, 12 …).
|
|
73
|
+
* 8. Falls back to `defaultSize` if no series matches.
|
|
74
|
+
*
|
|
75
|
+
* @param value - Numeric size to snap (in Tailwind spacing units, where 1 unit = 0.25 rem).
|
|
76
|
+
* @param defaultSize - Fallback `Size` token used when no series produces a valid match.
|
|
77
|
+
* @returns The nearest valid `Size` token.
|
|
78
|
+
*/
|
|
79
|
+
export const snapSize = (value: number, defaultSize: Size): Size => {
|
|
80
|
+
if (SIZE_VALUES.includes(value as Size)) {
|
|
81
|
+
return value as Size;
|
|
82
|
+
} else if (value <= 0) {
|
|
83
|
+
return 0;
|
|
84
|
+
} else if (value < 0.5) {
|
|
85
|
+
return 'px';
|
|
86
|
+
} else {
|
|
87
|
+
const wholeSeries = Math.floor(value);
|
|
88
|
+
const halfSeries = Math.floor(value * 2) / 2;
|
|
89
|
+
const doubleSeries = Math.floor(value / 2) * 2;
|
|
90
|
+
const quadrupleSeries = Math.floor(value / 4) * 4;
|
|
91
|
+
if (SIZE_VALUES.includes(halfSeries as Size)) {
|
|
92
|
+
return halfSeries as Size;
|
|
93
|
+
} else if (SIZE_VALUES.includes(wholeSeries as Size)) {
|
|
94
|
+
return wholeSeries as Size;
|
|
95
|
+
} else if (SIZE_VALUES.includes(doubleSeries as Size)) {
|
|
96
|
+
return doubleSeries as Size;
|
|
97
|
+
} else if (SIZE_VALUES.includes(quadrupleSeries as Size)) {
|
|
98
|
+
return quadrupleSeries as Size;
|
|
99
|
+
} else {
|
|
100
|
+
return defaultSize;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|