@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 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-dba9bdbd1f1d",
6
- "@domicile-desktop/system-apps": "0.0.0-alpha-dba9bdbd1f1d",
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-dba9bdbd1f1d"
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
- <span
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: 2.5,
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
- const content = (
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
- className={rowStyles}
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
- <Radio.Root aria-label={choice.label} className={rowStyles} value={index}>
279
- {content}
280
- </Radio.Root>
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",