@domicile-desktop/component-library 0.0.0-alpha-3ee3ca09c591 → 0.0.0-alpha-14e607160f4e
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/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-14e607160f4e",
|
|
6
|
+
"@domicile-desktop/system-apps": "0.0.0-alpha-14e607160f4e",
|
|
7
7
|
"@pandacss/dev": "^1.12.1",
|
|
8
8
|
"@phosphor-icons/react": "^2.1.10"
|
|
9
9
|
},
|
|
@@ -89,6 +89,7 @@
|
|
|
89
89
|
"files": [
|
|
90
90
|
"src/**",
|
|
91
91
|
"styled-system/**",
|
|
92
|
+
"!styled-system/styles.css",
|
|
92
93
|
"!src/**/*.test.ts",
|
|
93
94
|
"!src/**/*.test.tsx",
|
|
94
95
|
"!src/**/*.stories.tsx",
|
|
@@ -111,12 +112,12 @@
|
|
|
111
112
|
},
|
|
112
113
|
"scripts": {
|
|
113
114
|
"build:storybook": "storybook build",
|
|
114
|
-
"prepare": "panda codegen --silent",
|
|
115
|
+
"prepare": "panda codegen --silent && panda cssgen --silent",
|
|
115
116
|
"start:dev": "storybook dev -p 4000",
|
|
116
117
|
"start:prod": "http-server storybook-static -p 4000",
|
|
117
118
|
"test:types": "tsc --noEmit",
|
|
118
119
|
"test:unit": "bun test"
|
|
119
120
|
},
|
|
120
121
|
"type": "module",
|
|
121
|
-
"version": "0.0.0-alpha-
|
|
122
|
+
"version": "0.0.0-alpha-14e607160f4e"
|
|
122
123
|
}
|
|
@@ -9,12 +9,10 @@ import {
|
|
|
9
9
|
} from "@domicile-desktop/sdk/portal";
|
|
10
10
|
import { useState } from "react";
|
|
11
11
|
import { css } from "../../styled-system/css";
|
|
12
|
-
import { focusOnAttach } from "../_control/focusOnAttach";
|
|
13
|
-
import { FilePicker } from "../FilePicker/FilePicker";
|
|
14
12
|
import { ChooserMode } from "../FilePicker/file-request";
|
|
15
13
|
import type { Folders } from "../FilePicker/folders";
|
|
16
|
-
import { useScreenRegion } from "../Screen/DisplayProvider";
|
|
17
14
|
import { Select } from "../Select/Select";
|
|
15
|
+
import { ModalFilePicker } from "./ModalFilePicker";
|
|
18
16
|
|
|
19
17
|
/** A boolean choice's answers, as the portal spells them. */
|
|
20
18
|
const YES_NO = [
|
|
@@ -35,55 +33,50 @@ type Props = {
|
|
|
35
33
|
* takes the keyboard on open.
|
|
36
34
|
*/
|
|
37
35
|
export const FileChooserDialog = ({ answer, body, folders, screen }: Props) => {
|
|
38
|
-
const region = useScreenRegion(screen);
|
|
39
36
|
const [choices, setChoices] = useState<ReadonlyMap<string, string>>(
|
|
40
37
|
() => new Map(body.choices.map((choice) => [choice.id, choice.initial])),
|
|
41
38
|
);
|
|
42
39
|
return (
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
title: body.title,
|
|
84
|
-
}}
|
|
85
|
-
/>
|
|
86
|
-
</div>
|
|
40
|
+
<ModalFilePicker
|
|
41
|
+
request={{
|
|
42
|
+
accept: [],
|
|
43
|
+
acceptLabel: body.acceptLabel ?? acceptOf(body.mode),
|
|
44
|
+
cancel: () => {
|
|
45
|
+
answer(Answer.Canceled());
|
|
46
|
+
},
|
|
47
|
+
choose: (paths, filter) => {
|
|
48
|
+
answer(Answer.FileChooser({ choices, currentFilter: filter, paths }));
|
|
49
|
+
},
|
|
50
|
+
controls: (
|
|
51
|
+
<>
|
|
52
|
+
{body.mode === FileChooserMode.SaveFiles && (
|
|
53
|
+
<span
|
|
54
|
+
className={savesStyles}
|
|
55
|
+
>{`Saves ${body.files.join(", ")}`}</span>
|
|
56
|
+
)}
|
|
57
|
+
{body.choices.map((choice) => (
|
|
58
|
+
<Choice
|
|
59
|
+
choice={choice}
|
|
60
|
+
key={choice.id}
|
|
61
|
+
onChange={(value) => {
|
|
62
|
+
setChoices(new Map(choices).set(choice.id, value));
|
|
63
|
+
}}
|
|
64
|
+
value={choices.get(choice.id)}
|
|
65
|
+
/>
|
|
66
|
+
))}
|
|
67
|
+
</>
|
|
68
|
+
),
|
|
69
|
+
currentFilter: body.currentFilter,
|
|
70
|
+
currentFolder: body.currentFolder,
|
|
71
|
+
filters: body.filters.length === 0 ? undefined : body.filters,
|
|
72
|
+
folders,
|
|
73
|
+
home: body.home,
|
|
74
|
+
mode: chooserModeOf(body),
|
|
75
|
+
suggestedName: body.currentName ?? "",
|
|
76
|
+
title: body.title,
|
|
77
|
+
}}
|
|
78
|
+
screen={screen}
|
|
79
|
+
/>
|
|
87
80
|
);
|
|
88
81
|
};
|
|
89
82
|
|
|
@@ -116,11 +109,6 @@ const Choice = ({
|
|
|
116
109
|
/>
|
|
117
110
|
);
|
|
118
111
|
|
|
119
|
-
const regionStyles = css({
|
|
120
|
-
inset: 0,
|
|
121
|
-
position: "fixed",
|
|
122
|
-
});
|
|
123
|
-
|
|
124
112
|
const savesStyles = css({ color: "muted", fontSize: "sm" });
|
|
125
113
|
|
|
126
114
|
/** The picker's mode for what the application asked. */
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Dialog as BaseDialog } from "@base-ui/react/dialog";
|
|
2
|
+
import { useRef } from "react";
|
|
3
|
+
import { css } from "../../styled-system/css";
|
|
4
|
+
import { FilePicker } from "../FilePicker/FilePicker";
|
|
5
|
+
import type { FileRequest } from "../FilePicker/file-request";
|
|
6
|
+
import { useScreenRegion } from "../Screen/DisplayProvider";
|
|
7
|
+
|
|
8
|
+
type Props = {
|
|
9
|
+
request: FileRequest;
|
|
10
|
+
/** The display to cover. Without it, the whole page. */
|
|
11
|
+
screen: string | undefined;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A `FilePicker` over `screen`, modal like `ModalDialog`: in a portal at the
|
|
16
|
+
* dialogs' depth, with the rest of the page inert. Its box takes the keyboard
|
|
17
|
+
* on open.
|
|
18
|
+
*
|
|
19
|
+
* The picker answers every key and press itself, Escape included, so the
|
|
20
|
+
* dialog never closes on its own.
|
|
21
|
+
*/
|
|
22
|
+
export const ModalFilePicker = ({ request, screen }: Props) => {
|
|
23
|
+
const region = useScreenRegion(screen);
|
|
24
|
+
const box = useRef<HTMLInputElement>(null);
|
|
25
|
+
return (
|
|
26
|
+
<BaseDialog.Root onOpenChange={stayOpen} open>
|
|
27
|
+
<BaseDialog.Portal>
|
|
28
|
+
{/* The picker is the dialog; this box only places it. */}
|
|
29
|
+
<BaseDialog.Popup
|
|
30
|
+
className={regionStyles}
|
|
31
|
+
initialFocus={box}
|
|
32
|
+
role="presentation"
|
|
33
|
+
style={region}
|
|
34
|
+
>
|
|
35
|
+
<FilePicker ref={box} request={request} />
|
|
36
|
+
</BaseDialog.Popup>
|
|
37
|
+
</BaseDialog.Portal>
|
|
38
|
+
</BaseDialog.Root>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** Ignores base-ui's requests to close; see {@link ModalFilePicker}. */
|
|
43
|
+
const stayOpen = () => {
|
|
44
|
+
// The picker cancels through its request.
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// At the other dialogs' depth, over every window.
|
|
48
|
+
const regionStyles = css({
|
|
49
|
+
inset: 0,
|
|
50
|
+
position: "fixed",
|
|
51
|
+
zIndex: "modal",
|
|
52
|
+
});
|
|
@@ -17,16 +17,14 @@ import { PrinterIcon } from "@phosphor-icons/react/dist/ssr/Printer";
|
|
|
17
17
|
import { useState } from "react";
|
|
18
18
|
import { css } from "../../styled-system/css";
|
|
19
19
|
import { grid } from "../../styled-system/patterns";
|
|
20
|
-
import { focusOnAttach } from "../_control/focusOnAttach";
|
|
21
20
|
import { Button } from "../Button/Button";
|
|
22
21
|
import { Field } from "../Field/Field";
|
|
23
|
-
import { FilePicker } from "../FilePicker/FilePicker";
|
|
24
22
|
import { ChooserMode } from "../FilePicker/file-request";
|
|
25
23
|
import type { Folders } from "../FilePicker/folders";
|
|
26
24
|
import { Input } from "../Input/Input";
|
|
27
25
|
import { ModalDialog } from "../ModalDialog/ModalDialog";
|
|
28
|
-
import { useScreenRegion } from "../Screen/DisplayProvider";
|
|
29
26
|
import { Select } from "../Select/Select";
|
|
27
|
+
import { ModalFilePicker } from "./ModalFilePicker";
|
|
30
28
|
import { pageRanges } from "./page-ranges";
|
|
31
29
|
|
|
32
30
|
const SIDES = {
|
|
@@ -351,33 +349,28 @@ const PdfPicker = ({
|
|
|
351
349
|
folders: Folders;
|
|
352
350
|
options: PrintOptions;
|
|
353
351
|
screen: string | undefined;
|
|
354
|
-
}) =>
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
}}
|
|
377
|
-
/>
|
|
378
|
-
</div>
|
|
379
|
-
);
|
|
380
|
-
};
|
|
352
|
+
}) => (
|
|
353
|
+
<ModalFilePicker
|
|
354
|
+
request={{
|
|
355
|
+
accept: ["pdf"],
|
|
356
|
+
acceptLabel: "Save",
|
|
357
|
+
cancel: back,
|
|
358
|
+
choose: ([path]) => {
|
|
359
|
+
if (path === undefined) {
|
|
360
|
+
throw new Error("a save chose no file");
|
|
361
|
+
} else {
|
|
362
|
+
answer(Answer.SaveToPdf(path, options));
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
folders,
|
|
366
|
+
home: body.home,
|
|
367
|
+
mode: ChooserMode.Save,
|
|
368
|
+
suggestedName: pdfName(body.title),
|
|
369
|
+
title: "Save as PDF",
|
|
370
|
+
}}
|
|
371
|
+
screen={screen}
|
|
372
|
+
/>
|
|
373
|
+
);
|
|
381
374
|
|
|
382
375
|
const askerStyles = css({ color: "muted", fontSize: "sm", margin: 0 });
|
|
383
376
|
|
|
@@ -403,11 +396,6 @@ const pagesStyles = grid({
|
|
|
403
396
|
gridColumn: "1 / -1",
|
|
404
397
|
});
|
|
405
398
|
|
|
406
|
-
const regionStyles = css({
|
|
407
|
-
inset: 0,
|
|
408
|
-
position: "fixed",
|
|
409
|
-
});
|
|
410
|
-
|
|
411
399
|
/** `printer` for people. */
|
|
412
400
|
const nameOf = (printer: Printer): string =>
|
|
413
401
|
printer.description ?? printer.name;
|