@domicile-desktop/component-library 0.0.0-alpha-866cf8e38ce5 → 0.0.0-alpha-be59aae76f2f

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
@@ -39,6 +39,7 @@ Each export is `@domicile-desktop/component-library/<name>`.
39
39
  | `Popover` | Non-modal panel anchored to the control that opened it. |
40
40
  | `PopupBoundary` | Keeps every popup inside it (tooltips, popovers, menus, lists) within a box, such as a window's frame. Outside one, popups stay on their `Screen`. `Popover` and `Tooltip` take `spansWindows` to leave it. |
41
41
  | `Provider` | base-ui `DirectionProvider` wrapper. Every app roots its tree in it. |
42
+ | `QuickTile` | Quick settings tile: a labeled toggle in the accent color while on, with an arrow to its details. Drawn in `currentcolor`. |
42
43
  | `Screen` | Renders its children once per selected display. |
43
44
  | `DisplayProvider` | Supplies the host's displays to `Screen`. |
44
45
  | `display-source` | `Display` and `DisplaySource` types for `DisplayProvider`. |
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-866cf8e38ce5",
6
- "@domicile-desktop/system-apps": "0.0.0-alpha-866cf8e38ce5",
5
+ "@domicile-desktop/sdk": "0.0.0-alpha-be59aae76f2f",
6
+ "@domicile-desktop/system-apps": "0.0.0-alpha-be59aae76f2f",
7
7
  "@pandacss/dev": "^1.12.1",
8
8
  "@phosphor-icons/react": "^2.1.10"
9
9
  },
@@ -64,6 +64,7 @@
64
64
  "./PortalDialogs": "./src/PortalDialogs/PortalDialogs.tsx",
65
65
  "./Provider": "./src/Provider/Provider.tsx",
66
66
  "./pandacss-preset": "./src/pandacss-preset.ts",
67
+ "./QuickTile": "./src/QuickTile/QuickTile.tsx",
67
68
  "./Screen": "./src/Screen/Screen.tsx",
68
69
  "./Select": "./src/Select/Select.tsx",
69
70
  "./SlideOver": "./src/SlideOver/SlideOver.tsx",
@@ -120,5 +121,5 @@
120
121
  "test:unit": "bun test"
121
122
  },
122
123
  "type": "module",
123
- "version": "0.0.0-alpha-866cf8e38ce5"
124
+ "version": "0.0.0-alpha-be59aae76f2f"
124
125
  }
@@ -0,0 +1,183 @@
1
+ import { Toggle } from "@base-ui/react/toggle";
2
+ import { CaretRightIcon } from "@phosphor-icons/react/dist/ssr/CaretRight";
3
+ import type { ReactNode } from "react";
4
+
5
+ import { css, cva } from "../../styled-system/css";
6
+
7
+ /** What every tile shows: `icon`, then `label` over `detail`. */
8
+ type Content = {
9
+ /** A short status under the label, such as the network's name. */
10
+ detail?: string | undefined;
11
+ icon: ReactNode;
12
+ /** Names the tile, and its toggle. */
13
+ label: string;
14
+ };
15
+
16
+ /** A tile that switches something on and off, with details behind an arrow. */
17
+ type ToggleProps = Content & {
18
+ /** Ignores presses, such as on a locked desktop. */
19
+ disabled?: boolean | undefined;
20
+ /** Opens the details; without it there is no arrow. */
21
+ onMore?: (() => void) | undefined;
22
+ onPressedChange: (pressed: boolean) => void;
23
+ pressed: boolean;
24
+ };
25
+
26
+ /** A tile with nothing to switch, which opens its details. */
27
+ type MoreProps = Content & {
28
+ onMore: () => void;
29
+ onPressedChange?: undefined;
30
+ pressed?: undefined;
31
+ };
32
+
33
+ type Props = ToggleProps | MoreProps;
34
+
35
+ /**
36
+ * A quick settings tile: a labeled toggle in the accent color while on, and
37
+ * an arrow to its details when it has some. A tile with nothing to switch is
38
+ * one button that opens the details.
39
+ *
40
+ * Drawn in `currentcolor`, so it reads on a panel over the wallpaper.
41
+ */
42
+ export const QuickTile = (props: Props) =>
43
+ props.onPressedChange === undefined ? (
44
+ <button
45
+ aria-label={`${props.label} details`}
46
+ className={tileStyles({ pressed: false })}
47
+ onClick={props.onMore}
48
+ type="button"
49
+ >
50
+ <Face detail={props.detail} icon={props.icon} label={props.label} />
51
+ <CaretRightIcon aria-hidden className={caretStyles} size={14} />
52
+ </button>
53
+ ) : (
54
+ <span className={tileStyles({ pressed: props.pressed })}>
55
+ <Toggle
56
+ aria-label={props.label}
57
+ className={toggleStyles}
58
+ disabled={props.disabled}
59
+ onPressedChange={(pressed) => {
60
+ props.onPressedChange(pressed);
61
+ }}
62
+ pressed={props.pressed}
63
+ >
64
+ <Face detail={props.detail} icon={props.icon} label={props.label} />
65
+ </Toggle>
66
+ {props.onMore !== undefined && (
67
+ <button
68
+ aria-label={`${props.label} details`}
69
+ className={moreStyles}
70
+ onClick={props.onMore}
71
+ type="button"
72
+ >
73
+ <CaretRightIcon aria-hidden size={14} />
74
+ </button>
75
+ )}
76
+ </span>
77
+ );
78
+
79
+ const Face = ({ detail, icon, label }: Content) => (
80
+ <>
81
+ <span aria-hidden className={iconStyles}>
82
+ {icon}
83
+ </span>
84
+ <span className={textStyles}>
85
+ <span className={labelStyles}>{label}</span>
86
+ {detail !== undefined && <span className={detailStyles}>{detail}</span>}
87
+ </span>
88
+ </>
89
+ );
90
+
91
+ const tileStyles = cva({
92
+ base: {
93
+ _focusVisible: {
94
+ outline: "{spacing.0.5} solid {colors.accent}",
95
+ outlineOffset: 0.5,
96
+ },
97
+ alignItems: "center",
98
+ backgroundColor: "color-mix(in oklab, currentcolor 10%, transparent)",
99
+ borderRadius: "lg",
100
+ borderStyle: "none",
101
+ color: "inherit",
102
+ cursor: "pointer",
103
+ display: "flex",
104
+ gap: 2,
105
+ minBlockSize: 12,
106
+ minInlineSize: 0,
107
+ paddingInline: 2.5,
108
+ textAlign: "start",
109
+ transition: "background-color {durations.fast} {easings.default}",
110
+ },
111
+ variants: {
112
+ pressed: {
113
+ false: {},
114
+ true: { backgroundColor: "accent", color: "background" },
115
+ },
116
+ },
117
+ });
118
+
119
+ // Fills the tile but its arrow, so most of the tile switches.
120
+ const toggleStyles = css({
121
+ _disabled: { cursor: "default" },
122
+ _focusVisible: {
123
+ outline: "{spacing.0.5} solid {colors.accent}",
124
+ outlineOffset: 0.5,
125
+ },
126
+ alignItems: "center",
127
+ alignSelf: "stretch",
128
+ backgroundColor: "transparent",
129
+ borderStyle: "none",
130
+ color: "inherit",
131
+ cursor: "pointer",
132
+ display: "flex",
133
+ flex: 1,
134
+ gap: 2,
135
+ minInlineSize: 0,
136
+ padding: 0,
137
+ textAlign: "start",
138
+ });
139
+
140
+ const moreStyles = css({
141
+ _hover: {
142
+ backgroundColor: "color-mix(in oklab, currentcolor 16%, transparent)",
143
+ },
144
+ alignItems: "center",
145
+ backgroundColor: "transparent",
146
+ blockSize: 6,
147
+ borderRadius: "full",
148
+ borderStyle: "none",
149
+ color: "inherit",
150
+ cursor: "pointer",
151
+ display: "inline-flex",
152
+ flexShrink: 0,
153
+ inlineSize: 6,
154
+ justifyContent: "center",
155
+ marginInlineEnd: -1,
156
+ padding: 0,
157
+ });
158
+
159
+ const caretStyles = css({ flexShrink: 0, marginInlineStart: "auto" });
160
+
161
+ const iconStyles = css({ display: "inline-flex", flexShrink: 0 });
162
+
163
+ const textStyles = css({
164
+ display: "flex",
165
+ flexDirection: "column",
166
+ minInlineSize: 0,
167
+ paddingBlock: 1.5,
168
+ });
169
+
170
+ const labelStyles = css({
171
+ fontSize: "sm",
172
+ fontWeight: "semibold",
173
+ lineHeight: "tight",
174
+ });
175
+
176
+ const detailStyles = css({
177
+ fontSize: "xs",
178
+ lineHeight: "tight",
179
+ opacity: "inactive",
180
+ overflow: "hidden",
181
+ textOverflow: "ellipsis",
182
+ whiteSpace: "nowrap",
183
+ });
@@ -1,4 +1,5 @@
1
1
  import { Dialog as BaseDialog } from "@base-ui/react/dialog";
2
+ import { CaretLeftIcon } from "@phosphor-icons/react/dist/ssr/CaretLeft";
2
3
  import { XIcon } from "@phosphor-icons/react/dist/ssr/X";
3
4
  import type { ReactElement, ReactNode } from "react";
4
5
  import { css, cva } from "../../styled-system/css";
@@ -20,6 +21,13 @@ type Props = ExtendProps<
20
21
  {
21
22
  children: ReactNode;
22
23
  footer?: ReactNode | undefined;
24
+ /**
25
+ * Keeps the panel in the page, hidden, while closed, so what it holds
26
+ * keeps running, such as a web page.
27
+ */
28
+ keepMounted?: boolean | undefined;
29
+ /** Shows a back button before the title, for a page within the panel. */
30
+ onBack?: (() => void) | undefined;
23
31
  /**
24
32
  * Name of the display to slide in on and dim. Without it, a page spanning
25
33
  * several monitors uses the rightmost edge and dims them all. Needs a
@@ -28,22 +36,27 @@ type Props = ExtendProps<
28
36
  screen?: string | undefined;
29
37
  title?: ReactNode | undefined;
30
38
  trigger?: ReactElement | undefined;
39
+ /** Wider, for a page with a list and its content side by side. */
40
+ wide?: boolean | undefined;
31
41
  }
32
42
  >;
33
43
 
34
44
  const SlideOverComponent = ({
35
45
  children,
36
46
  footer,
47
+ keepMounted = false,
48
+ onBack,
37
49
  screen,
38
50
  title,
39
51
  trigger,
52
+ wide = false,
40
53
  ...rootProps
41
54
  }: Props) => {
42
55
  const region = useScreenRegion(screen);
43
56
  return (
44
57
  <BaseDialog.Root {...rootProps}>
45
58
  {trigger !== undefined && <BaseDialog.Trigger render={trigger} />}
46
- <BaseDialog.Portal>
59
+ <BaseDialog.Portal keepMounted={keepMounted}>
47
60
  {/*
48
61
  On the panel's screen only, as in `ModalDialog`. Data attribute for
49
62
  tests.
@@ -54,8 +67,16 @@ const SlideOverComponent = ({
54
67
  style={region}
55
68
  />
56
69
  <BaseDialog.Viewport className={viewportStyles} style={region}>
57
- <BaseDialog.Popup className={popupStyles}>
70
+ <BaseDialog.Popup
71
+ className={popupStyles}
72
+ data-wide={wide ? "" : undefined}
73
+ >
58
74
  <header className={headerStyles}>
75
+ {onBack !== undefined && (
76
+ <Button label="Back" onClick={onBack} variant="ghost">
77
+ <CaretLeftIcon />
78
+ </Button>
79
+ )}
59
80
  {title !== undefined && (
60
81
  <BaseDialog.Title className={titleStyles}>
61
82
  {title}
@@ -126,6 +147,7 @@ const popupStyles = flex({
126
147
  transform: "translateX(100%)",
127
148
  transition: "transform {durations.fast} {easings.in}",
128
149
  },
150
+ "&[data-wide]": { inlineSize: "min({spacing.240}, 92cqw)" },
129
151
  backgroundColor: "card",
130
152
  blockSize: "100%",
131
153
  borderInlineStartColor: "border",
@@ -147,8 +169,10 @@ const headerStyles = flex({
147
169
  paddingInline: 4,
148
170
  });
149
171
 
172
+ // Fills the header between the back and close buttons.
150
173
  const titleStyles = css({
151
174
  color: "foreground",
175
+ flex: 1,
152
176
  fontSize: "md",
153
177
  fontWeight: "semibold",
154
178
  lineHeight: "snug",