@domicile-desktop/component-library 0.0.0-alpha-dba9bdbd1f1d → 0.0.0-alpha-44d0983a58ce
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/README.md +1 -0
- package/package.json +4 -3
- package/src/ChoiceRow/ChoiceRow.tsx +124 -0
- package/src/LevelToast/LevelToast.tsx +71 -15
- package/src/PortalDialogs/SourcePicker.tsx +15 -72
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@ Each export is `@domicile-desktop/component-library/<name>`.
|
|
|
22
22
|
| `Avatar` | Avatar with an initials or gradient fallback. |
|
|
23
23
|
| `Button` | Button or link, with variants and sizes. |
|
|
24
24
|
| `Card` | Raised surface with optional title and footer. |
|
|
25
|
+
| `ChoiceRow` | Rows of a pick list, with icon, name and detail, framed while picked: `RadioRow` in `ChoiceList` for one, `CheckboxRow` for several. Take the panel's white text in an `overPhoto` popover. |
|
|
25
26
|
| `Drilldown` | View that slides a second panel in from the side, with a back button. |
|
|
26
27
|
| `Field` | Label, control and validation message. |
|
|
27
28
|
| `FilePicker` | Keyboard-first file picker over its positioned parent. |
|
package/package.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"dependencies": {
|
|
3
3
|
"@base-ui/react": "^1.8.0",
|
|
4
4
|
"@cprussin/option-result": "^2.0.1",
|
|
5
|
-
"@domicile-desktop/sdk": "0.0.0-alpha-
|
|
6
|
-
"@domicile-desktop/system-apps": "0.0.0-alpha-
|
|
5
|
+
"@domicile-desktop/sdk": "0.0.0-alpha-44d0983a58ce",
|
|
6
|
+
"@domicile-desktop/system-apps": "0.0.0-alpha-44d0983a58ce",
|
|
7
7
|
"@pandacss/dev": "^1.12.1",
|
|
8
8
|
"@phosphor-icons/react": "^2.1.10"
|
|
9
9
|
},
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"./appearance": "./src/ThemeSwitch/appearance.ts",
|
|
42
42
|
"./Button": "./src/Button/Button.tsx",
|
|
43
43
|
"./Card": "./src/Card/Card.tsx",
|
|
44
|
+
"./ChoiceRow": "./src/ChoiceRow/ChoiceRow.tsx",
|
|
44
45
|
"./ContextMenu": "./src/ContextMenu/ContextMenu.tsx",
|
|
45
46
|
"./control-sizes": "./src/control-sizes.ts",
|
|
46
47
|
"./DisplayProvider": "./src/Screen/DisplayProvider.tsx",
|
|
@@ -116,5 +117,5 @@
|
|
|
116
117
|
"test:unit": "bun test"
|
|
117
118
|
},
|
|
118
119
|
"type": "module",
|
|
119
|
-
"version": "0.0.0-alpha-
|
|
120
|
+
"version": "0.0.0-alpha-44d0983a58ce"
|
|
120
121
|
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Checkbox } from "@base-ui/react/checkbox";
|
|
2
|
+
import { Radio } from "@base-ui/react/radio";
|
|
3
|
+
import { RadioGroup } from "@base-ui/react/radio-group";
|
|
4
|
+
import type { ReactNode } from "react";
|
|
5
|
+
|
|
6
|
+
import { css } from "../../styled-system/css";
|
|
7
|
+
import { flex } from "../../styled-system/patterns";
|
|
8
|
+
import type { ExtendProps } from "../extend-props";
|
|
9
|
+
|
|
10
|
+
/** What a row shows: `icon`, then `name` over `detail`. */
|
|
11
|
+
type Content = {
|
|
12
|
+
detail?: string | undefined;
|
|
13
|
+
icon: ReactNode;
|
|
14
|
+
name: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type RadioRowProps = ExtendProps<typeof Radio.Root, Content>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A row of a list that picks one thing, framed in the accent color while
|
|
21
|
+
* picked. Goes in a `RadioGroup`, such as `ChoiceList`. The screenshot
|
|
22
|
+
* picker's rows are these.
|
|
23
|
+
*/
|
|
24
|
+
export const RadioRow = ({ detail, icon, name, ...props }: RadioRowProps) => (
|
|
25
|
+
<Radio.Root {...props} className={rowStyles}>
|
|
26
|
+
<RowContent detail={detail} icon={icon} name={name} />
|
|
27
|
+
</Radio.Root>
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
type CheckboxRowProps = ExtendProps<typeof Checkbox.Root, Content>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A row of a list that picks several things, framed like {@link RadioRow}.
|
|
34
|
+
* Goes in a `CheckboxGroup`.
|
|
35
|
+
*/
|
|
36
|
+
export const CheckboxRow = ({
|
|
37
|
+
detail,
|
|
38
|
+
icon,
|
|
39
|
+
name,
|
|
40
|
+
...props
|
|
41
|
+
}: CheckboxRowProps) => (
|
|
42
|
+
<Checkbox.Root {...props} className={rowStyles}>
|
|
43
|
+
<RowContent detail={detail} icon={icon} name={name} />
|
|
44
|
+
</Checkbox.Root>
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
type ChoiceListProps<Value> = ExtendProps<typeof RadioGroup<Value>>;
|
|
48
|
+
|
|
49
|
+
/** A column of {@link RadioRow}s, one of which is picked. */
|
|
50
|
+
export const ChoiceList = <Value,>(props: ChoiceListProps<Value>) => (
|
|
51
|
+
<RadioGroup<Value> {...props} className={listStyles} />
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const RowContent = ({ detail, icon, name }: Content) => (
|
|
55
|
+
<>
|
|
56
|
+
{icon}
|
|
57
|
+
<span className={textStyles}>
|
|
58
|
+
<span className={nameStyles}>{name}</span>
|
|
59
|
+
{detail !== undefined && <span className={detailStyles}>{detail}</span>}
|
|
60
|
+
</span>
|
|
61
|
+
</>
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const listStyles = flex({ direction: "column", gap: 1 });
|
|
65
|
+
|
|
66
|
+
// Over the wallpaper, as in a bar's popover, the rows take the panel's white
|
|
67
|
+
// text, since `foreground` and `muted` follow the theme and the panel does not.
|
|
68
|
+
const rowStyles = css({
|
|
69
|
+
_checked: {
|
|
70
|
+
backgroundColor: "color-mix(in oklab, {colors.accent} 20%, transparent)",
|
|
71
|
+
borderColor: "accent",
|
|
72
|
+
},
|
|
73
|
+
_hoverEnabled: {
|
|
74
|
+
backgroundColor: "color-mix(in oklab, {colors.foreground} 6%, transparent)",
|
|
75
|
+
},
|
|
76
|
+
"[data-tone=overPhoto] &": {
|
|
77
|
+
"& > :is(svg, img)": { color: "inherit", opacity: 0.75 },
|
|
78
|
+
"&:hover:not([data-checked])": {
|
|
79
|
+
backgroundColor: "color-mix(in oklab, currentcolor 12%, transparent)",
|
|
80
|
+
},
|
|
81
|
+
color: "inherit",
|
|
82
|
+
},
|
|
83
|
+
"& > :is(svg, img)": {
|
|
84
|
+
blockSize: 5,
|
|
85
|
+
color: "muted",
|
|
86
|
+
flexShrink: 0,
|
|
87
|
+
inlineSize: 5,
|
|
88
|
+
},
|
|
89
|
+
alignItems: "center",
|
|
90
|
+
backgroundColor: "transparent",
|
|
91
|
+
border: "1px solid transparent",
|
|
92
|
+
borderRadius: "md",
|
|
93
|
+
color: "foreground",
|
|
94
|
+
cursor: "pointer",
|
|
95
|
+
display: "flex",
|
|
96
|
+
flexShrink: 0,
|
|
97
|
+
font: "inherit",
|
|
98
|
+
gap: 2.5,
|
|
99
|
+
minInlineSize: 0,
|
|
100
|
+
paddingBlock: 1.5,
|
|
101
|
+
paddingInline: 2.5,
|
|
102
|
+
textAlign: "start",
|
|
103
|
+
transition: "background-color {durations.fast} {easings.out}",
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const textStyles = flex({ direction: "column", minInlineSize: 0 });
|
|
107
|
+
|
|
108
|
+
// The bar's popovers keep their smaller text.
|
|
109
|
+
const nameStyles = css({
|
|
110
|
+
"[data-tone=overPhoto] &": { fontSize: "inherit" },
|
|
111
|
+
fontSize: "sm",
|
|
112
|
+
overflow: "hidden",
|
|
113
|
+
textOverflow: "ellipsis",
|
|
114
|
+
whiteSpace: "nowrap",
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const detailStyles = css({
|
|
118
|
+
"[data-tone=overPhoto] &": { color: "inherit", opacity: 0.75 },
|
|
119
|
+
color: "muted",
|
|
120
|
+
fontSize: "xs",
|
|
121
|
+
overflow: "hidden",
|
|
122
|
+
textOverflow: "ellipsis",
|
|
123
|
+
whiteSpace: "nowrap",
|
|
124
|
+
});
|
|
@@ -28,7 +28,65 @@ type Props = {
|
|
|
28
28
|
* `animationend`, not a timer, so the duration lives only in the stylesheet.
|
|
29
29
|
* The caller positions it; it ignores the pointer.
|
|
30
30
|
*/
|
|
31
|
-
export const LevelToast = ({ changes, icon, label, level, muted }: Props) =>
|
|
31
|
+
export const LevelToast = ({ changes, icon, label, level, muted }: Props) => (
|
|
32
|
+
<HeldToast
|
|
33
|
+
changes={changes}
|
|
34
|
+
icon={icon}
|
|
35
|
+
label={label}
|
|
36
|
+
muted={muted}
|
|
37
|
+
value={muted ? "Muted" : `${String(Math.round(level * 100))}%`}
|
|
38
|
+
>
|
|
39
|
+
<span
|
|
40
|
+
aria-valuemax={100}
|
|
41
|
+
aria-valuemin={0}
|
|
42
|
+
aria-valuenow={Math.round(level * 100)}
|
|
43
|
+
className={meterStyles}
|
|
44
|
+
data-muted={muted ? "" : undefined}
|
|
45
|
+
role="meter"
|
|
46
|
+
// Inline because the level is a runtime value Panda cannot extract.
|
|
47
|
+
style={{ "--level-toast-level": level } as CSSProperties}
|
|
48
|
+
/>
|
|
49
|
+
</HeldToast>
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
type ChoiceProps = Pick<Props, "changes" | "icon" | "label"> & {
|
|
53
|
+
/** What was chosen, such as a keymap's name. */
|
|
54
|
+
choice: string;
|
|
55
|
+
/** A line under the label saying more of it, if any. */
|
|
56
|
+
detail?: string | undefined;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A toast like {@link LevelToast} naming a choice just made, such as the
|
|
61
|
+
* keymap a key switched to, with no meter.
|
|
62
|
+
*/
|
|
63
|
+
export const ChoiceToast = ({
|
|
64
|
+
changes,
|
|
65
|
+
choice,
|
|
66
|
+
detail,
|
|
67
|
+
icon,
|
|
68
|
+
label,
|
|
69
|
+
}: ChoiceProps) => (
|
|
70
|
+
<HeldToast
|
|
71
|
+
changes={changes}
|
|
72
|
+
icon={icon}
|
|
73
|
+
label={label}
|
|
74
|
+
muted={false}
|
|
75
|
+
value={choice}
|
|
76
|
+
>
|
|
77
|
+
{detail !== undefined && <span className={detailStyles}>{detail}</span>}
|
|
78
|
+
</HeldToast>
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
/** The held, fading card both toasts are, with `children` under the label. */
|
|
82
|
+
const HeldToast = ({
|
|
83
|
+
changes,
|
|
84
|
+
children,
|
|
85
|
+
icon,
|
|
86
|
+
label,
|
|
87
|
+
muted,
|
|
88
|
+
value,
|
|
89
|
+
}: Omit<Props, "level"> & { children?: ReactNode; value: string }) => {
|
|
32
90
|
const [goneAfter, setGoneAfter] = useState<number | undefined>();
|
|
33
91
|
|
|
34
92
|
return goneAfter === changes ? undefined : (
|
|
@@ -49,20 +107,9 @@ export const LevelToast = ({ changes, icon, label, level, muted }: Props) => {
|
|
|
49
107
|
<span className={readoutStyles}>
|
|
50
108
|
<span className={labelRowStyles}>
|
|
51
109
|
<span>{label}</span>
|
|
52
|
-
<span className={valueStyles}>
|
|
53
|
-
{muted ? "Muted" : `${String(Math.round(level * 100))}%`}
|
|
54
|
-
</span>
|
|
110
|
+
<span className={valueStyles}>{value}</span>
|
|
55
111
|
</span>
|
|
56
|
-
|
|
57
|
-
aria-valuemax={100}
|
|
58
|
-
aria-valuemin={0}
|
|
59
|
-
aria-valuenow={Math.round(level * 100)}
|
|
60
|
-
className={meterStyles}
|
|
61
|
-
data-muted={muted ? "" : undefined}
|
|
62
|
-
role="meter"
|
|
63
|
-
// Inline because the level is a runtime value Panda cannot extract.
|
|
64
|
-
style={{ "--level-toast-level": level } as CSSProperties}
|
|
65
|
-
/>
|
|
112
|
+
{children}
|
|
66
113
|
</span>
|
|
67
114
|
</span>
|
|
68
115
|
</output>
|
|
@@ -112,7 +159,7 @@ const readoutStyles = css({
|
|
|
112
159
|
display: "flex",
|
|
113
160
|
flex: 1,
|
|
114
161
|
flexDirection: "column",
|
|
115
|
-
gap:
|
|
162
|
+
gap: 0.5,
|
|
116
163
|
minInlineSize: 0,
|
|
117
164
|
});
|
|
118
165
|
|
|
@@ -147,6 +194,15 @@ const meterStyles = css({
|
|
|
147
194
|
backgroundColor: "color-mix(in oklab, {colors.foreground} 14%, transparent)",
|
|
148
195
|
blockSize: 2,
|
|
149
196
|
borderRadius: "full",
|
|
197
|
+
marginBlockStart: 2,
|
|
150
198
|
overflow: "hidden",
|
|
151
199
|
position: "relative",
|
|
152
200
|
});
|
|
201
|
+
|
|
202
|
+
const detailStyles = css({
|
|
203
|
+
color: "muted",
|
|
204
|
+
fontSize: "sm",
|
|
205
|
+
overflow: "hidden",
|
|
206
|
+
textOverflow: "ellipsis",
|
|
207
|
+
whiteSpace: "nowrap",
|
|
208
|
+
});
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { Checkbox } from "@base-ui/react/checkbox";
|
|
2
1
|
import { CheckboxGroup } from "@base-ui/react/checkbox-group";
|
|
3
|
-
import { Radio } from "@base-ui/react/radio";
|
|
4
2
|
import { RadioGroup } from "@base-ui/react/radio-group";
|
|
5
3
|
import type { Result } from "@cprussin/option-result";
|
|
6
4
|
import type { FrozenDesk, ShotRect } from "@domicile-desktop/sdk/portal";
|
|
@@ -10,6 +8,7 @@ import type { CSSProperties, PointerEvent, ReactNode } from "react";
|
|
|
10
8
|
import { useState } from "react";
|
|
11
9
|
import { css } from "../../styled-system/css";
|
|
12
10
|
import { center, flex } from "../../styled-system/patterns";
|
|
11
|
+
import { CheckboxRow, RadioRow } from "../ChoiceRow/ChoiceRow";
|
|
13
12
|
import type { FramePoint } from "./frame-point";
|
|
14
13
|
import { draggedArea, framePoint } from "./frame-point";
|
|
15
14
|
import type { DrawPreview } from "./useWindowPreview";
|
|
@@ -254,32 +253,24 @@ const Row = ({
|
|
|
254
253
|
choice: Choice;
|
|
255
254
|
index: number;
|
|
256
255
|
multiple: boolean;
|
|
257
|
-
}) =>
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
{choice.icon}
|
|
261
|
-
<span className={rowTextStyles}>
|
|
262
|
-
<span className={rowNameStyles}>{choice.name}</span>
|
|
263
|
-
{choice.detail !== undefined && (
|
|
264
|
-
<span className={rowDetailStyles}>{choice.detail}</span>
|
|
265
|
-
)}
|
|
266
|
-
</span>
|
|
267
|
-
</>
|
|
268
|
-
);
|
|
269
|
-
return multiple ? (
|
|
270
|
-
<Checkbox.Root
|
|
256
|
+
}) =>
|
|
257
|
+
multiple ? (
|
|
258
|
+
<CheckboxRow
|
|
271
259
|
aria-label={choice.label}
|
|
272
|
-
|
|
260
|
+
detail={choice.detail}
|
|
261
|
+
icon={choice.icon}
|
|
262
|
+
name={choice.name}
|
|
273
263
|
value={String(index)}
|
|
274
|
-
|
|
275
|
-
{content}
|
|
276
|
-
</Checkbox.Root>
|
|
264
|
+
/>
|
|
277
265
|
) : (
|
|
278
|
-
<
|
|
279
|
-
{
|
|
280
|
-
|
|
266
|
+
<RadioRow
|
|
267
|
+
aria-label={choice.label}
|
|
268
|
+
detail={choice.detail}
|
|
269
|
+
icon={choice.icon}
|
|
270
|
+
name={choice.name}
|
|
271
|
+
value={index}
|
|
272
|
+
/>
|
|
281
273
|
);
|
|
282
|
-
};
|
|
283
274
|
|
|
284
275
|
/**
|
|
285
276
|
* A window drawn alone from `src`, fitted in `box`. With `onDrag`, a part of
|
|
@@ -475,54 +466,6 @@ const sectionLabelStyles = css({
|
|
|
475
466
|
textTransform: "uppercase",
|
|
476
467
|
});
|
|
477
468
|
|
|
478
|
-
const rowStyles = css({
|
|
479
|
-
_checked: {
|
|
480
|
-
backgroundColor: "color-mix(in oklab, {colors.accent} 20%, transparent)",
|
|
481
|
-
borderColor: "accent",
|
|
482
|
-
},
|
|
483
|
-
_hoverEnabled: {
|
|
484
|
-
backgroundColor: "color-mix(in oklab, {colors.foreground} 6%, transparent)",
|
|
485
|
-
},
|
|
486
|
-
"& > :is(svg, img)": {
|
|
487
|
-
blockSize: 5,
|
|
488
|
-
color: "muted",
|
|
489
|
-
flexShrink: 0,
|
|
490
|
-
inlineSize: 5,
|
|
491
|
-
},
|
|
492
|
-
alignItems: "center",
|
|
493
|
-
backgroundColor: "transparent",
|
|
494
|
-
border: "1px solid transparent",
|
|
495
|
-
borderRadius: "md",
|
|
496
|
-
color: "foreground",
|
|
497
|
-
cursor: "pointer",
|
|
498
|
-
display: "flex",
|
|
499
|
-
flexShrink: 0,
|
|
500
|
-
font: "inherit",
|
|
501
|
-
gap: 2.5,
|
|
502
|
-
minInlineSize: 0,
|
|
503
|
-
paddingBlock: 1.5,
|
|
504
|
-
paddingInline: 2.5,
|
|
505
|
-
textAlign: "start",
|
|
506
|
-
transition: "background-color {durations.fast} {easings.out}",
|
|
507
|
-
});
|
|
508
|
-
|
|
509
|
-
const rowTextStyles = flex({ direction: "column", minInlineSize: 0 });
|
|
510
|
-
|
|
511
|
-
const rowNameStyles = css({
|
|
512
|
-
fontSize: "sm",
|
|
513
|
-
overflow: "hidden",
|
|
514
|
-
textOverflow: "ellipsis",
|
|
515
|
-
whiteSpace: "nowrap",
|
|
516
|
-
});
|
|
517
|
-
|
|
518
|
-
const rowDetailStyles = css({
|
|
519
|
-
color: "muted",
|
|
520
|
-
fontSize: "xs",
|
|
521
|
-
overflow: "hidden",
|
|
522
|
-
textOverflow: "ellipsis",
|
|
523
|
-
whiteSpace: "nowrap",
|
|
524
|
-
});
|
|
525
|
-
|
|
526
469
|
const stageStyles = flex({
|
|
527
470
|
align: "center",
|
|
528
471
|
backgroundColor: "background",
|