@domicile-desktop/component-library 0.0.0-alpha-dc90c701b510 → 0.0.0-alpha-e1a71faac2bd
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 +4 -3
- package/src/FluidItem/FluidItem.tsx +170 -0
- package/styled-system/styles.css +32 -4
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-e1a71faac2bd",
|
|
6
|
+
"@domicile-desktop/system-apps": "0.0.0-alpha-e1a71faac2bd",
|
|
7
7
|
"@pandacss/dev": "^1.12.1",
|
|
8
8
|
"@phosphor-icons/react": "^2.1.10"
|
|
9
9
|
},
|
|
@@ -76,6 +76,7 @@
|
|
|
76
76
|
"./EmptyState": "./src/EmptyState/EmptyState.tsx",
|
|
77
77
|
"./Field": "./src/Field/Field.tsx",
|
|
78
78
|
"./FilePicker": "./src/FilePicker/FilePicker.tsx",
|
|
79
|
+
"./FluidItem": "./src/FluidItem/FluidItem.tsx",
|
|
79
80
|
"./file-request": "./src/FilePicker/file-request.ts",
|
|
80
81
|
"./folders": "./src/FilePicker/folders.ts",
|
|
81
82
|
"./font": "./src/fonts/font.ts",
|
|
@@ -164,5 +165,5 @@
|
|
|
164
165
|
"test:unit": "bun test"
|
|
165
166
|
},
|
|
166
167
|
"type": "module",
|
|
167
|
-
"version": "0.0.0-alpha-
|
|
168
|
+
"version": "0.0.0-alpha-e1a71faac2bd"
|
|
168
169
|
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { useLayoutEffect, useRef } from "react";
|
|
3
|
+
|
|
4
|
+
import { css, cva } from "../../styled-system/css";
|
|
5
|
+
import "../stylesheet";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Calls `onWidth` with `element`'s width now and whenever it changes, until
|
|
9
|
+
* the returned function stops it.
|
|
10
|
+
*/
|
|
11
|
+
export type ObserveWidth = (
|
|
12
|
+
element: Element,
|
|
13
|
+
onWidth: (width: number) => void,
|
|
14
|
+
) => () => void;
|
|
15
|
+
|
|
16
|
+
type Props = {
|
|
17
|
+
/** What it shows. Nothing, such as `null` or `false`, closes it. */
|
|
18
|
+
children: ReactNode;
|
|
19
|
+
/**
|
|
20
|
+
* The space between it and its neighbors: `narrow` is 1.5 spacing steps,
|
|
21
|
+
* as between icons; `wide` is 4, as between bar items. Half sits on
|
|
22
|
+
* each side and closes with it, so the row sets no gap of its own and
|
|
23
|
+
* pulls its ends in by half of this.
|
|
24
|
+
*/
|
|
25
|
+
gap?: Gap | undefined;
|
|
26
|
+
/** Injectable for tests; defaults to a `ResizeObserver`. */
|
|
27
|
+
observeWidth?: ObserveWidth | undefined;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** The gaps a row of `FluidItem`s can have. */
|
|
31
|
+
export type Gap = "none" | "narrow" | "wide";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* An item in a row that eases between widths: it opens and fades in when
|
|
35
|
+
* its content appears, follows its content as that grows and shrinks, and
|
|
36
|
+
* closes and fades out when its content goes. What goes is still drawn, out
|
|
37
|
+
* of reach, until it has faded.
|
|
38
|
+
*
|
|
39
|
+
* Its content can be any component, and needs no motion of its own: one that
|
|
40
|
+
* returns nothing to hide is enough.
|
|
41
|
+
*/
|
|
42
|
+
export const FluidItem = ({
|
|
43
|
+
children,
|
|
44
|
+
gap = "none",
|
|
45
|
+
observeWidth = resizeObserverWidth,
|
|
46
|
+
}: Props) => {
|
|
47
|
+
const slot = useRef<HTMLSpanElement>(null);
|
|
48
|
+
const content = useRef<HTMLSpanElement>(null);
|
|
49
|
+
const live = useRef<HTMLSpanElement>(null);
|
|
50
|
+
const leaving = useRef<HTMLSpanElement>(null);
|
|
51
|
+
|
|
52
|
+
// In a layout effect, and by hand rather than through state, so what
|
|
53
|
+
// leaves is back in place before the frame that would show it gone.
|
|
54
|
+
useLayoutEffect(() => {
|
|
55
|
+
const parts = {
|
|
56
|
+
content: content.current,
|
|
57
|
+
leaving: leaving.current,
|
|
58
|
+
live: live.current,
|
|
59
|
+
slot: slot.current,
|
|
60
|
+
};
|
|
61
|
+
if (
|
|
62
|
+
parts.slot === null ||
|
|
63
|
+
parts.content === null ||
|
|
64
|
+
parts.live === null ||
|
|
65
|
+
parts.leaving === null
|
|
66
|
+
) {
|
|
67
|
+
throw new Error("FluidItem rendered without its parts");
|
|
68
|
+
}
|
|
69
|
+
const { leaving: left, live: shown, slot: box } = parts;
|
|
70
|
+
let width = 0;
|
|
71
|
+
const draw = () => {
|
|
72
|
+
const showing = shown.hasChildNodes();
|
|
73
|
+
box.toggleAttribute("data-shown", showing);
|
|
74
|
+
box.style.setProperty("--fluid-width", `${showing ? width : 0}px`);
|
|
75
|
+
};
|
|
76
|
+
const mutations = new MutationObserver((records) => {
|
|
77
|
+
if (shown.hasChildNodes()) {
|
|
78
|
+
left.replaceChildren();
|
|
79
|
+
} else {
|
|
80
|
+
const gone = records
|
|
81
|
+
.flatMap((record) => [...record.removedNodes])
|
|
82
|
+
.filter((node) => !node.isConnected);
|
|
83
|
+
if (gone.length > 0) {
|
|
84
|
+
left.replaceChildren(...gone);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
draw();
|
|
88
|
+
});
|
|
89
|
+
mutations.observe(shown, { childList: true });
|
|
90
|
+
const stopWatchingWidth = observeWidth(parts.content, (measured) => {
|
|
91
|
+
width = measured;
|
|
92
|
+
draw();
|
|
93
|
+
});
|
|
94
|
+
return () => {
|
|
95
|
+
mutations.disconnect();
|
|
96
|
+
stopWatchingWidth();
|
|
97
|
+
};
|
|
98
|
+
}, [observeWidth]);
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<span
|
|
102
|
+
className={slotStyles}
|
|
103
|
+
onTransitionEnd={(event) => {
|
|
104
|
+
// It has faded out, so what left can go.
|
|
105
|
+
if (
|
|
106
|
+
event.target === event.currentTarget &&
|
|
107
|
+
event.propertyName === "opacity" &&
|
|
108
|
+
!event.currentTarget.hasAttribute("data-shown")
|
|
109
|
+
) {
|
|
110
|
+
leaving.current?.replaceChildren();
|
|
111
|
+
}
|
|
112
|
+
}}
|
|
113
|
+
ref={slot}
|
|
114
|
+
>
|
|
115
|
+
<span className={contentStyles({ gap })} ref={content}>
|
|
116
|
+
<span className={layerStyles} ref={live}>
|
|
117
|
+
{children}
|
|
118
|
+
</span>
|
|
119
|
+
<span aria-hidden className={layerStyles} inert ref={leaving} />
|
|
120
|
+
</span>
|
|
121
|
+
</span>
|
|
122
|
+
);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// The width eases to the measured one. Before the first measurement it is
|
|
126
|
+
// `auto`, which does not animate, so what shows from the start does not open.
|
|
127
|
+
// Clipped across only, so nothing above or below is cut.
|
|
128
|
+
const slotStyles = css({
|
|
129
|
+
"&:not([data-shown])": { opacity: 0 },
|
|
130
|
+
display: "inline-flex",
|
|
131
|
+
flexShrink: 0,
|
|
132
|
+
inlineSize: "var(--fluid-width, auto)",
|
|
133
|
+
opacity: 1,
|
|
134
|
+
overflowX: "clip",
|
|
135
|
+
transition:
|
|
136
|
+
"inline-size {durations.normal} {easings.in-out}, opacity {durations.normal} {easings.default}",
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Its own width, so it can be measured whatever the slot is eased to. The gap
|
|
140
|
+
// is inside, so it closes with the content.
|
|
141
|
+
const contentStyles = cva({
|
|
142
|
+
base: {
|
|
143
|
+
alignItems: "center",
|
|
144
|
+
display: "inline-flex",
|
|
145
|
+
flexShrink: 0,
|
|
146
|
+
inlineSize: "max-content",
|
|
147
|
+
},
|
|
148
|
+
variants: {
|
|
149
|
+
gap: {
|
|
150
|
+
narrow: { paddingInline: 0.75 },
|
|
151
|
+
none: {},
|
|
152
|
+
wide: { paddingInline: 2 },
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Lays the content out as if the wrapper were not there.
|
|
158
|
+
const layerStyles = css({ display: "contents" });
|
|
159
|
+
|
|
160
|
+
/** `ObserveWidth` through a `ResizeObserver`. */
|
|
161
|
+
const resizeObserverWidth: ObserveWidth = (element, onWidth) => {
|
|
162
|
+
const observer = new ResizeObserver(() => {
|
|
163
|
+
onWidth(element.getBoundingClientRect().width);
|
|
164
|
+
});
|
|
165
|
+
observer.observe(element);
|
|
166
|
+
onWidth(element.getBoundingClientRect().width);
|
|
167
|
+
return () => {
|
|
168
|
+
observer.disconnect();
|
|
169
|
+
};
|
|
170
|
+
};
|
package/styled-system/styles.css
CHANGED
|
@@ -9974,6 +9974,18 @@
|
|
|
9974
9974
|
border-inline-end: 1px solid var(--eieEZo);
|
|
9975
9975
|
}
|
|
9976
9976
|
|
|
9977
|
+
.pWVvC {
|
|
9978
|
+
margin-inline: calc(var(--ebuyxV) * -1);
|
|
9979
|
+
}
|
|
9980
|
+
|
|
9981
|
+
.dMMjth {
|
|
9982
|
+
gap: wide;
|
|
9983
|
+
}
|
|
9984
|
+
|
|
9985
|
+
.hcgsql {
|
|
9986
|
+
transition: inline-size var(--cWTXGg) var(--gSAVQW), opacity var(--cWTXGg) var(--drrWcF);
|
|
9987
|
+
}
|
|
9988
|
+
|
|
9977
9989
|
.bCtaNN {
|
|
9978
9990
|
transition: background-color var(--mUhz) var(--drrWcF);
|
|
9979
9991
|
}
|
|
@@ -11024,6 +11036,18 @@
|
|
|
11024
11036
|
margin-inline-start: calc(var(--ebuyxV) * -1);
|
|
11025
11037
|
}
|
|
11026
11038
|
|
|
11039
|
+
.VEKaB {
|
|
11040
|
+
inline-size: var(--fluid-width, auto);
|
|
11041
|
+
}
|
|
11042
|
+
|
|
11043
|
+
.hKujQS {
|
|
11044
|
+
display: contents;
|
|
11045
|
+
}
|
|
11046
|
+
|
|
11047
|
+
.gxuBUc {
|
|
11048
|
+
inline-size: max-content;
|
|
11049
|
+
}
|
|
11050
|
+
|
|
11027
11051
|
.jDTsqy {
|
|
11028
11052
|
block-size: var(--eUDwWi);
|
|
11029
11053
|
}
|
|
@@ -11466,10 +11490,6 @@
|
|
|
11466
11490
|
font-size: inherit;
|
|
11467
11491
|
}
|
|
11468
11492
|
|
|
11469
|
-
.gxuBUc {
|
|
11470
|
-
inline-size: max-content;
|
|
11471
|
-
}
|
|
11472
|
-
|
|
11473
11493
|
.gNGMSf {
|
|
11474
11494
|
max-inline-size: var(--available-width);
|
|
11475
11495
|
}
|
|
@@ -12003,6 +12023,10 @@
|
|
|
12003
12023
|
right: 100%;
|
|
12004
12024
|
}
|
|
12005
12025
|
|
|
12026
|
+
.yOEYy {
|
|
12027
|
+
overflow-x: clip;
|
|
12028
|
+
}
|
|
12029
|
+
|
|
12006
12030
|
.ewrolA {
|
|
12007
12031
|
width: standard;
|
|
12008
12032
|
}
|
|
@@ -12747,6 +12771,10 @@
|
|
|
12747
12771
|
font-weight: var(--hmvyHR);
|
|
12748
12772
|
}
|
|
12749
12773
|
|
|
12774
|
+
.jlsPPt:not([data-shown]) {
|
|
12775
|
+
opacity: 0;
|
|
12776
|
+
}
|
|
12777
|
+
|
|
12750
12778
|
.YJgQP:is(:disabled, [disabled], [data-disabled], [aria-disabled=true]) {
|
|
12751
12779
|
cursor: default;
|
|
12752
12780
|
}
|