@multiplatform.one/components 7.81.1 → 7.82.0
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/dist/cjs/charts/BarChart.cjs +1 -0
- package/dist/cjs/charts/BarChart.native.js +1 -0
- package/dist/cjs/charts/BarChart.native.js.map +1 -1
- package/dist/cjs/charts/ChartDataView.cjs +89 -9
- package/dist/cjs/charts/ChartDataView.native.js +94 -9
- package/dist/cjs/charts/ChartDataView.native.js.map +1 -1
- package/dist/cjs/charts/ChartSurface.cjs +26 -8
- package/dist/cjs/charts/ChartSurface.native.js +27 -9
- package/dist/cjs/charts/ChartSurface.native.js.map +1 -1
- package/dist/cjs/charts/chartHeight.cjs +1 -1
- package/dist/cjs/charts/chartHeight.native.js +1 -1
- package/dist/cjs/charts/chartHeight.native.js.map +1 -1
- package/dist/cjs/charts/composeChart.cjs +1 -1
- package/dist/cjs/charts/composeChart.native.js +1 -1
- package/dist/cjs/charts/composeChart.native.js.map +1 -1
- package/dist/cjs/code/CodeBlock/index.cjs +38 -31
- package/dist/cjs/code/CodeBlock/index.native.js +38 -31
- package/dist/cjs/code/CodeBlock/index.native.js.map +1 -1
- package/dist/cjs/code/LogView.cjs +142 -0
- package/dist/cjs/code/LogView.native.js +151 -0
- package/dist/cjs/code/LogView.native.js.map +1 -0
- package/dist/cjs/code/index.cjs +2 -1
- package/dist/cjs/code/index.native.js +1 -0
- package/dist/cjs/code/index.native.js.map +1 -1
- package/dist/cjs/layouts/EmptyState.cjs +9 -43
- package/dist/cjs/layouts/EmptyState.native.js +9 -45
- package/dist/cjs/layouts/EmptyState.native.js.map +1 -1
- package/dist/cjs/layouts/PageTemplates.cjs +61 -0
- package/dist/cjs/layouts/PageTemplates.native.js +62 -0
- package/dist/cjs/layouts/PageTemplates.native.js.map +1 -1
- package/dist/cjs/layouts/barTitleSpan.cjs +54 -0
- package/dist/cjs/layouts/barTitleSpan.native.js +78 -0
- package/dist/cjs/layouts/barTitleSpan.native.js.map +1 -0
- package/dist/cjs/layouts/page.cjs +130 -41
- package/dist/cjs/layouts/page.native.js +143 -45
- package/dist/cjs/layouts/page.native.js.map +1 -1
- package/dist/cjs/views/ImageGrid.cjs +9 -0
- package/dist/cjs/views/ImageGrid.native.js +12 -1
- package/dist/cjs/views/ImageGrid.native.js.map +1 -1
- package/dist/esm/charts/BarChart.mjs +1 -0
- package/dist/esm/charts/BarChart.mjs.map +1 -1
- package/dist/esm/charts/BarChart.native.js +1 -0
- package/dist/esm/charts/BarChart.native.js.map +1 -1
- package/dist/esm/charts/ChartDataView.mjs +83 -3
- package/dist/esm/charts/ChartDataView.mjs.map +1 -1
- package/dist/esm/charts/ChartDataView.native.js +88 -3
- package/dist/esm/charts/ChartDataView.native.js.map +1 -1
- package/dist/esm/charts/ChartSurface.mjs +26 -8
- package/dist/esm/charts/ChartSurface.mjs.map +1 -1
- package/dist/esm/charts/ChartSurface.native.js +27 -9
- package/dist/esm/charts/ChartSurface.native.js.map +1 -1
- package/dist/esm/charts/chartHeight.mjs +1 -1
- package/dist/esm/charts/chartHeight.mjs.map +1 -1
- package/dist/esm/charts/chartHeight.native.js +1 -1
- package/dist/esm/charts/chartHeight.native.js.map +1 -1
- package/dist/esm/charts/composeChart.mjs +1 -1
- package/dist/esm/charts/composeChart.mjs.map +1 -1
- package/dist/esm/charts/composeChart.native.js +1 -1
- package/dist/esm/charts/composeChart.native.js.map +1 -1
- package/dist/esm/code/CodeBlock/index.mjs +39 -32
- package/dist/esm/code/CodeBlock/index.mjs.map +1 -1
- package/dist/esm/code/CodeBlock/index.native.js +39 -32
- package/dist/esm/code/CodeBlock/index.native.js.map +1 -1
- package/dist/esm/code/LogView.mjs +117 -0
- package/dist/esm/code/LogView.mjs.map +1 -0
- package/dist/esm/code/LogView.native.js +123 -0
- package/dist/esm/code/LogView.native.js.map +1 -0
- package/dist/esm/code/index.mjs +1 -0
- package/dist/esm/code/index.mjs.map +1 -1
- package/dist/esm/code/index.native.js +1 -0
- package/dist/esm/code/index.native.js.map +1 -1
- package/dist/esm/layouts/EmptyState.mjs +9 -43
- package/dist/esm/layouts/EmptyState.mjs.map +1 -1
- package/dist/esm/layouts/EmptyState.native.js +9 -45
- package/dist/esm/layouts/EmptyState.native.js.map +1 -1
- package/dist/esm/layouts/PageTemplates.mjs +61 -1
- package/dist/esm/layouts/PageTemplates.mjs.map +1 -1
- package/dist/esm/layouts/PageTemplates.native.js +62 -1
- package/dist/esm/layouts/PageTemplates.native.js.map +1 -1
- package/dist/esm/layouts/barTitleSpan.mjs +27 -0
- package/dist/esm/layouts/barTitleSpan.mjs.map +1 -0
- package/dist/esm/layouts/barTitleSpan.native.js +48 -0
- package/dist/esm/layouts/barTitleSpan.native.js.map +1 -0
- package/dist/esm/layouts/page.mjs +132 -43
- package/dist/esm/layouts/page.mjs.map +1 -1
- package/dist/esm/layouts/page.native.js +145 -47
- package/dist/esm/layouts/page.native.js.map +1 -1
- package/dist/esm/views/ImageGrid.mjs +10 -1
- package/dist/esm/views/ImageGrid.mjs.map +1 -1
- package/dist/esm/views/ImageGrid.native.js +13 -2
- package/dist/esm/views/ImageGrid.native.js.map +1 -1
- package/dist/jsx/charts/BarChart.mjs +1 -0
- package/dist/jsx/charts/BarChart.mjs.map +1 -1
- package/dist/jsx/charts/BarChart.native.js +1 -0
- package/dist/jsx/charts/BarChart.native.js.map +1 -1
- package/dist/jsx/charts/ChartDataView.mjs +83 -3
- package/dist/jsx/charts/ChartDataView.mjs.map +1 -1
- package/dist/jsx/charts/ChartDataView.native.js +94 -9
- package/dist/jsx/charts/ChartDataView.native.js.map +1 -1
- package/dist/jsx/charts/ChartSurface.mjs +26 -8
- package/dist/jsx/charts/ChartSurface.mjs.map +1 -1
- package/dist/jsx/charts/ChartSurface.native.js +27 -9
- package/dist/jsx/charts/ChartSurface.native.js.map +1 -1
- package/dist/jsx/charts/chartHeight.mjs +1 -1
- package/dist/jsx/charts/chartHeight.mjs.map +1 -1
- package/dist/jsx/charts/chartHeight.native.js +1 -1
- package/dist/jsx/charts/chartHeight.native.js.map +1 -1
- package/dist/jsx/charts/composeChart.mjs +1 -1
- package/dist/jsx/charts/composeChart.mjs.map +1 -1
- package/dist/jsx/charts/composeChart.native.js +1 -1
- package/dist/jsx/charts/composeChart.native.js.map +1 -1
- package/dist/jsx/code/CodeBlock/index.mjs +39 -32
- package/dist/jsx/code/CodeBlock/index.mjs.map +1 -1
- package/dist/jsx/code/CodeBlock/index.native.js +38 -31
- package/dist/jsx/code/CodeBlock/index.native.js.map +1 -1
- package/dist/jsx/code/LogView.mjs +117 -0
- package/dist/jsx/code/LogView.mjs.map +1 -0
- package/dist/jsx/code/LogView.native.js +151 -0
- package/dist/jsx/code/LogView.native.js.map +1 -0
- package/dist/jsx/code/index.mjs +1 -0
- package/dist/jsx/code/index.mjs.map +1 -1
- package/dist/jsx/code/index.native.js +1 -0
- package/dist/jsx/code/index.native.js.map +1 -1
- package/dist/jsx/layouts/EmptyState.mjs +9 -43
- package/dist/jsx/layouts/EmptyState.mjs.map +1 -1
- package/dist/jsx/layouts/EmptyState.native.js +9 -45
- package/dist/jsx/layouts/EmptyState.native.js.map +1 -1
- package/dist/jsx/layouts/PageTemplates.mjs +61 -1
- package/dist/jsx/layouts/PageTemplates.mjs.map +1 -1
- package/dist/jsx/layouts/PageTemplates.native.js +62 -0
- package/dist/jsx/layouts/PageTemplates.native.js.map +1 -1
- package/dist/jsx/layouts/barTitleSpan.mjs +27 -0
- package/dist/jsx/layouts/barTitleSpan.mjs.map +1 -0
- package/dist/jsx/layouts/barTitleSpan.native.js +78 -0
- package/dist/jsx/layouts/barTitleSpan.native.js.map +1 -0
- package/dist/jsx/layouts/page.mjs +132 -43
- package/dist/jsx/layouts/page.mjs.map +1 -1
- package/dist/jsx/layouts/page.native.js +143 -45
- package/dist/jsx/layouts/page.native.js.map +1 -1
- package/dist/jsx/views/ImageGrid.mjs +10 -1
- package/dist/jsx/views/ImageGrid.mjs.map +1 -1
- package/dist/jsx/views/ImageGrid.native.js +12 -1
- package/dist/jsx/views/ImageGrid.native.js.map +1 -1
- package/package.json +9 -9
- package/src/charts/BarChart.tsx +8 -2
- package/src/charts/ChartDataView.spec.tsx +23 -2
- package/src/charts/ChartDataView.tsx +87 -6
- package/src/charts/ChartSurface.tsx +17 -8
- package/src/charts/chartHeight.ts +8 -2
- package/src/charts/composeChart.ts +3 -2
- package/src/code/CodeBlock/CodeBlock.copy.spec.tsx +13 -0
- package/src/code/CodeBlock/index.tsx +46 -30
- package/src/code/LogView.tsx +127 -0
- package/src/code/index.ts +1 -0
- package/src/layouts/DenseContent.stories.tsx +93 -0
- package/src/layouts/EmptyState.spec.tsx +2 -0
- package/src/layouts/EmptyState.tsx +6 -39
- package/src/layouts/PageHeader.stories.tsx +238 -38
- package/src/layouts/PageTemplates.tsx +90 -0
- package/src/layouts/barTitleSpan.spec.ts +35 -0
- package/src/layouts/barTitleSpan.ts +44 -0
- package/src/layouts/glyphCentre.spec.tsx +218 -0
- package/src/layouts/navBar.spec.tsx +58 -2
- package/src/layouts/page.tsx +126 -34
- package/src/views/ImageGrid.tsx +19 -2
- package/types/charts/BarChart.d.ts +1 -1
- package/types/charts/BarChart.d.ts.map +1 -1
- package/types/charts/ChartDataView.d.ts +3 -1
- package/types/charts/ChartDataView.d.ts.map +1 -1
- package/types/charts/ChartSurface.d.ts.map +1 -1
- package/types/charts/chartHeight.d.ts +2 -1
- package/types/charts/chartHeight.d.ts.map +1 -1
- package/types/charts/composeChart.d.ts +2 -1
- package/types/charts/composeChart.d.ts.map +1 -1
- package/types/code/CodeBlock/index.d.ts.map +1 -1
- package/types/code/LogView.d.ts +31 -0
- package/types/code/LogView.d.ts.map +1 -0
- package/types/code/index.d.ts +1 -0
- package/types/code/index.d.ts.map +1 -1
- package/types/layouts/EmptyState.d.ts.map +1 -1
- package/types/layouts/PageTemplates.d.ts +26 -0
- package/types/layouts/PageTemplates.d.ts.map +1 -1
- package/types/layouts/barTitleSpan.d.ts +29 -0
- package/types/layouts/barTitleSpan.d.ts.map +1 -0
- package/types/layouts/page.d.ts +6 -1
- package/types/layouts/page.d.ts.map +1 -1
- package/types/views/ImageGrid.d.ts.map +1 -1
|
@@ -10,8 +10,7 @@
|
|
|
10
10
|
* not a second description. `intent="error"` is sober failure chrome
|
|
11
11
|
* (DG-ST-01) — never for a successful empty dataset.
|
|
12
12
|
*/
|
|
13
|
-
import {
|
|
14
|
-
import { CopyIcon } from "@phosphor-icons/react";
|
|
13
|
+
import { Spinner } from "@multiplatform.one/forms";
|
|
15
14
|
import {
|
|
16
15
|
IconSlot,
|
|
17
16
|
Intent,
|
|
@@ -26,7 +25,6 @@ import {
|
|
|
26
25
|
H2,
|
|
27
26
|
Paragraph,
|
|
28
27
|
type SizeTokens,
|
|
29
|
-
Text,
|
|
30
28
|
View,
|
|
31
29
|
XStack,
|
|
32
30
|
YStack,
|
|
@@ -34,8 +32,7 @@ import {
|
|
|
34
32
|
type YStackProps,
|
|
35
33
|
} from "tamagui";
|
|
36
34
|
import { componentColors, sectionHeading } from "../componentColors";
|
|
37
|
-
import {
|
|
38
|
-
import { useCopyFeedback } from "../feedback/copy";
|
|
35
|
+
import { CodeBlock } from "../code/CodeBlock";
|
|
39
36
|
|
|
40
37
|
export interface EmptyStateProps extends Omit<YStackProps, "size"> {
|
|
41
38
|
/** One short title: what is true now ("No Pokémon yet"), not an instruction. */
|
|
@@ -102,43 +99,13 @@ function StateSymbol({
|
|
|
102
99
|
}
|
|
103
100
|
|
|
104
101
|
/**
|
|
105
|
-
* The refusal gate's one command
|
|
106
|
-
*
|
|
102
|
+
* The refusal gate's one command, as a code block with its copy inside
|
|
103
|
+
* (LC-267).
|
|
107
104
|
*/
|
|
108
105
|
function CommandLine({ command }: { command: string }) {
|
|
109
|
-
const { knobProps } = useResolvedKnobs();
|
|
110
|
-
const { region, copy } = useCopyFeedback();
|
|
111
106
|
return (
|
|
112
|
-
<YStack data-empty-command=""
|
|
113
|
-
<
|
|
114
|
-
alignItems="center"
|
|
115
|
-
maxWidth="100%"
|
|
116
|
-
backgroundColor="$color3"
|
|
117
|
-
paddingLeft="$3"
|
|
118
|
-
paddingRight="$1"
|
|
119
|
-
paddingVertical="$1"
|
|
120
|
-
{...knobProps.containerRadius}
|
|
121
|
-
{...knobProps.gapSm}
|
|
122
|
-
>
|
|
123
|
-
<Text
|
|
124
|
-
{...knobProps.body}
|
|
125
|
-
fontFamily="$mono"
|
|
126
|
-
color={componentColors.text.primary}
|
|
127
|
-
numberOfLines={1}
|
|
128
|
-
flexShrink={1}
|
|
129
|
-
>
|
|
130
|
-
{command}
|
|
131
|
-
</Text>
|
|
132
|
-
<Button
|
|
133
|
-
nested
|
|
134
|
-
chromeless
|
|
135
|
-
flexShrink={0}
|
|
136
|
-
icon={<CopyIcon />}
|
|
137
|
-
aria-label="Copy to clipboard"
|
|
138
|
-
onPress={() => void copy(command)}
|
|
139
|
-
/>
|
|
140
|
-
</XStack>
|
|
141
|
-
<NotifyRegion scope="field" region={region} size="$3" />
|
|
107
|
+
<YStack data-empty-command="" alignSelf="center" {...readingWidthStyle()}>
|
|
108
|
+
<CodeBlock language="text">{command}</CodeBlock>
|
|
142
109
|
</YStack>
|
|
143
110
|
);
|
|
144
111
|
}
|
|
@@ -1,27 +1,30 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react-native-web-vite";
|
|
2
2
|
// Catalog Button (G-15 story honesty / LC-18): raw tamagui buttons ignored
|
|
3
3
|
// the knobs and pinned a non-house radius (UX-P06).
|
|
4
|
-
import { Button, IconButton
|
|
4
|
+
import { Button, IconButton } from "@multiplatform.one/forms";
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
CpuIcon,
|
|
7
|
+
DatabaseIcon,
|
|
8
8
|
MagnifyingGlassIcon,
|
|
9
|
+
MemoryIcon,
|
|
9
10
|
PlusIcon,
|
|
10
11
|
SignOutIcon,
|
|
11
|
-
TrashIcon,
|
|
12
12
|
UserIcon,
|
|
13
13
|
} from "@phosphor-icons/react";
|
|
14
14
|
import { Preset } from "@multiplatform.one/theme";
|
|
15
15
|
import type { ReactNode } from "react";
|
|
16
|
-
import { XStack, YStack } from "tamagui";
|
|
16
|
+
import { Circle, Theme, View, XStack, YStack } from "tamagui";
|
|
17
17
|
import { Avatar } from "../Avatar";
|
|
18
18
|
import { Card } from "../Card";
|
|
19
|
+
import { themed } from "../icons/themed";
|
|
20
|
+
import { Chip } from "../Chip";
|
|
21
|
+
import { CodeBlock } from "../code/CodeBlock";
|
|
19
22
|
import { DropdownMenu } from "../DropdownMenu";
|
|
20
23
|
import { Popover } from "../Popover";
|
|
21
24
|
import { SchemeToggle } from "../SchemeToggle";
|
|
22
25
|
import { PopoverContent } from "../surfaces";
|
|
23
26
|
import { Tabs } from "../Tabs";
|
|
24
|
-
import { PageBackButton, PageHeader, Screen, ScreenToolbar } from "./page";
|
|
27
|
+
import { PageBackButton, PageHeader, PageSection, Screen, ScreenToolbar } from "./page";
|
|
25
28
|
import { Paragraph, SizableText } from "../Text";
|
|
26
29
|
|
|
27
30
|
const meta: Meta<typeof PageHeader> = {
|
|
@@ -174,23 +177,237 @@ export const LongTitleNarrow: Story = {
|
|
|
174
177
|
),
|
|
175
178
|
};
|
|
176
179
|
|
|
177
|
-
// FZ-
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
|
|
180
|
+
// FZ-8442: the SHC console's Nodes screen as it is now (shc
|
|
181
|
+
// features/console/src/manage/app/NodesPane.tsx and ManageApp.tsx's
|
|
182
|
+
// NodesPlace): sites as sections, each node a front panel whose whole face
|
|
183
|
+
// links to its detail, where the verbs live. The "+" opens one panel over
|
|
184
|
+
// itself holding the join command as a code block (LC-112, LC-267). The bar
|
|
185
|
+
// keeps every LC-176 icon-button context.
|
|
186
|
+
const faceplate = {
|
|
187
|
+
ear: 18,
|
|
188
|
+
screw: 9,
|
|
189
|
+
slot: 1.5,
|
|
190
|
+
led: 14,
|
|
191
|
+
ledGap: 2,
|
|
192
|
+
digitWidth: 8,
|
|
193
|
+
digitHeight: 14,
|
|
194
|
+
segment: 2,
|
|
195
|
+
digitGap: 2,
|
|
196
|
+
window: 6,
|
|
197
|
+
} as const;
|
|
198
|
+
const LED_COUNT = 20;
|
|
199
|
+
const bayGlyph = { cpu: themed(CpuIcon), memory: themed(MemoryIcon), disk: themed(DatabaseIcon) };
|
|
200
|
+
|
|
201
|
+
function Screw({ angle }: { angle: number }) {
|
|
202
|
+
const inner = faceplate.screw - 2;
|
|
203
|
+
return (
|
|
204
|
+
<Circle
|
|
205
|
+
size={faceplate.screw}
|
|
206
|
+
borderWidth={1}
|
|
207
|
+
borderColor="$color9"
|
|
208
|
+
backgroundColor="$color6"
|
|
209
|
+
position="relative"
|
|
210
|
+
>
|
|
211
|
+
<View
|
|
212
|
+
position="absolute"
|
|
213
|
+
top={(inner - faceplate.slot) / 2}
|
|
214
|
+
left={0}
|
|
215
|
+
width={inner}
|
|
216
|
+
height={faceplate.slot}
|
|
217
|
+
backgroundColor="$color11"
|
|
218
|
+
rotate={`${angle}deg`}
|
|
219
|
+
/>
|
|
220
|
+
</Circle>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function RackEar({ side, angles }: { side: "left" | "right"; angles: [number, number] }) {
|
|
225
|
+
return (
|
|
226
|
+
<YStack
|
|
227
|
+
width={faceplate.ear}
|
|
228
|
+
alignSelf="stretch"
|
|
229
|
+
alignItems="center"
|
|
230
|
+
justifyContent="space-between"
|
|
231
|
+
paddingVertical="$3"
|
|
232
|
+
backgroundColor="$color4"
|
|
233
|
+
borderColor="$color6"
|
|
234
|
+
borderLeftWidth={side === "right" ? 1 : 0}
|
|
235
|
+
borderRightWidth={side === "left" ? 1 : 0}
|
|
236
|
+
aria-hidden
|
|
237
|
+
>
|
|
238
|
+
<Screw angle={angles[0]} />
|
|
239
|
+
<Screw angle={angles[1]} />
|
|
240
|
+
</YStack>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const segmentsOf: Record<string, string> = {
|
|
245
|
+
"0": "abcdef",
|
|
246
|
+
"1": "bc",
|
|
247
|
+
"2": "abdeg",
|
|
248
|
+
"3": "abcdg",
|
|
249
|
+
"4": "bcfg",
|
|
250
|
+
"5": "acdfg",
|
|
251
|
+
"6": "acdefg",
|
|
252
|
+
"7": "abc",
|
|
253
|
+
"8": "abcdefg",
|
|
254
|
+
"9": "abcdfg",
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
function segmentBox(name: string, width: number) {
|
|
258
|
+
const { digitHeight: h, segment: t } = faceplate;
|
|
259
|
+
const middle = (h - t) / 2;
|
|
260
|
+
const stroke = (h + t) / 2;
|
|
261
|
+
const boxes: Record<string, { top: number; left: number; width: number; height: number }> = {
|
|
262
|
+
a: { top: 0, left: 0, width, height: t },
|
|
263
|
+
b: { top: 0, left: width - t, width: t, height: stroke },
|
|
264
|
+
c: { top: middle, left: width - t, width: t, height: stroke },
|
|
265
|
+
d: { top: h - t, left: 0, width, height: t },
|
|
266
|
+
e: { top: middle, left: 0, width: t, height: stroke },
|
|
267
|
+
f: { top: 0, left: 0, width: t, height: stroke },
|
|
268
|
+
g: { top: middle, left: 0, width, height: t },
|
|
269
|
+
};
|
|
270
|
+
return boxes[name]!;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function AddressDisplay({ address }: { address: string }) {
|
|
274
|
+
return (
|
|
275
|
+
<Theme name="dark">
|
|
276
|
+
<XStack
|
|
277
|
+
role="img"
|
|
278
|
+
aria-label={address}
|
|
279
|
+
alignItems="center"
|
|
280
|
+
gap={faceplate.digitGap}
|
|
281
|
+
backgroundColor="$color1"
|
|
282
|
+
borderColor="$color6"
|
|
283
|
+
borderWidth={1}
|
|
284
|
+
borderRadius="$2"
|
|
285
|
+
padding={faceplate.window}
|
|
286
|
+
>
|
|
287
|
+
{[...address].map((char, index) => {
|
|
288
|
+
if (char === "." || char === ":")
|
|
289
|
+
return (
|
|
290
|
+
<YStack
|
|
291
|
+
key={index}
|
|
292
|
+
width={faceplate.segment}
|
|
293
|
+
height={faceplate.digitHeight}
|
|
294
|
+
justifyContent={char === "." ? "flex-end" : "space-evenly"}
|
|
295
|
+
>
|
|
296
|
+
<View
|
|
297
|
+
width={faceplate.segment}
|
|
298
|
+
height={faceplate.segment}
|
|
299
|
+
backgroundColor="$blue10"
|
|
300
|
+
/>
|
|
301
|
+
{char === ":" ? (
|
|
302
|
+
<View
|
|
303
|
+
width={faceplate.segment}
|
|
304
|
+
height={faceplate.segment}
|
|
305
|
+
backgroundColor="$blue10"
|
|
306
|
+
/>
|
|
307
|
+
) : null}
|
|
308
|
+
</YStack>
|
|
309
|
+
);
|
|
310
|
+
const width = char === "1" ? faceplate.segment : faceplate.digitWidth;
|
|
311
|
+
return (
|
|
312
|
+
<View key={index} width={width} height={faceplate.digitHeight} position="relative">
|
|
313
|
+
{[...(segmentsOf[char] ?? "")].map((name) => (
|
|
314
|
+
<View
|
|
315
|
+
key={name}
|
|
316
|
+
position="absolute"
|
|
317
|
+
{...segmentBox(name, width)}
|
|
318
|
+
backgroundColor="$blue10"
|
|
319
|
+
/>
|
|
320
|
+
))}
|
|
321
|
+
</View>
|
|
322
|
+
);
|
|
323
|
+
})}
|
|
324
|
+
</XStack>
|
|
325
|
+
</Theme>
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const ledInk = { lit: "$color11", warning: "$orange9", critical: "$red9" } as const;
|
|
330
|
+
const lampAt = (index: number) =>
|
|
331
|
+
index >= LED_COUNT * 0.9 ? "critical" : index >= LED_COUNT * 0.7 ? "warning" : "lit";
|
|
332
|
+
|
|
333
|
+
function LedBar({
|
|
334
|
+
bay,
|
|
335
|
+
label,
|
|
336
|
+
percent,
|
|
337
|
+
amount,
|
|
338
|
+
}: {
|
|
339
|
+
bay: keyof typeof bayGlyph;
|
|
340
|
+
label: string;
|
|
341
|
+
percent: number;
|
|
342
|
+
amount: string;
|
|
343
|
+
}) {
|
|
344
|
+
const lit = Math.round((percent / 100) * LED_COUNT);
|
|
345
|
+
const Glyph = bayGlyph[bay];
|
|
183
346
|
return (
|
|
184
|
-
<YStack gap="$
|
|
185
|
-
<XStack
|
|
186
|
-
<
|
|
187
|
-
<SizableText
|
|
347
|
+
<YStack gap="$1.5" role="meter" aria-label={label} aria-valuenow={percent}>
|
|
348
|
+
<XStack alignItems="center" gap="$2">
|
|
349
|
+
<Glyph size={16} weight="bold" color="$color11" />
|
|
350
|
+
<SizableText flex={1} size="$2" fontVariant={["tabular-nums"]} numberOfLines={1}>
|
|
351
|
+
{amount}
|
|
352
|
+
</SizableText>
|
|
353
|
+
<SizableText size="$2" fontWeight="700" fontVariant={["tabular-nums"]}>
|
|
354
|
+
{`${percent}%`}
|
|
355
|
+
</SizableText>
|
|
356
|
+
</XStack>
|
|
357
|
+
<XStack gap={faceplate.ledGap} aria-hidden>
|
|
358
|
+
{Array.from({ length: LED_COUNT }, (_, index) => (
|
|
359
|
+
<View
|
|
360
|
+
key={index}
|
|
361
|
+
flex={1}
|
|
362
|
+
height={faceplate.led}
|
|
363
|
+
borderRadius={1}
|
|
364
|
+
backgroundColor={ledInk[lampAt(index)]}
|
|
365
|
+
opacity={index < lit ? 1 : 0.18}
|
|
366
|
+
/>
|
|
367
|
+
))}
|
|
188
368
|
</XStack>
|
|
189
|
-
<Progress value={value} />
|
|
190
369
|
</YStack>
|
|
191
370
|
);
|
|
192
371
|
}
|
|
193
372
|
|
|
373
|
+
function NodeFaceplate() {
|
|
374
|
+
return (
|
|
375
|
+
<Card padding="$0" overflow="hidden" flexDirection="row" alignItems="stretch">
|
|
376
|
+
<RackEar side="left" angles={[38, 121]} />
|
|
377
|
+
<YStack flex={1} gap="$3" padding="$3">
|
|
378
|
+
<XStack alignItems="center" justifyContent="space-between" gap="$2.5">
|
|
379
|
+
<XStack gap="$1.5" alignItems="center" aria-hidden>
|
|
380
|
+
<Circle size="$0.75" backgroundColor="$green9" />
|
|
381
|
+
<Circle size="$0.5" backgroundColor="$green9" />
|
|
382
|
+
</XStack>
|
|
383
|
+
<AddressDisplay address="10.64.96.1:4003" />
|
|
384
|
+
</XStack>
|
|
385
|
+
<XStack alignItems="center" gap="$2.5">
|
|
386
|
+
<SizableText size="$4" fontWeight="700" numberOfLines={1}>
|
|
387
|
+
incus01
|
|
388
|
+
</SizableText>
|
|
389
|
+
<SizableText size="$1" fontWeight="600">
|
|
390
|
+
incus
|
|
391
|
+
</SizableText>
|
|
392
|
+
</XStack>
|
|
393
|
+
<LedBar bay="cpu" label="CPU" percent={12} amount="2.83/24 cores" />
|
|
394
|
+
<LedBar bay="memory" label="Memory" percent={41} amount="25.3/61.9 GiB" />
|
|
395
|
+
<LedBar bay="disk" label="Disk" percent={74} amount="352/476 GiB" />
|
|
396
|
+
<XStack gap="$1.5" flexWrap="wrap">
|
|
397
|
+
<Chip size="$2">AMD Ryzen 9 7900</Chip>
|
|
398
|
+
<Chip size="$2">x86_64</Chip>
|
|
399
|
+
</XStack>
|
|
400
|
+
<Avatar.Group max={7} size={36} circular={false}>
|
|
401
|
+
{["pokemon", "keycloak", "nextcloud", "fizzy", "gitlab", "openobserve"].map((app) => (
|
|
402
|
+
<Avatar key={app} name={app} />
|
|
403
|
+
))}
|
|
404
|
+
</Avatar.Group>
|
|
405
|
+
</YStack>
|
|
406
|
+
<RackEar side="right" angles={[92, 7]} />
|
|
407
|
+
</Card>
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
194
411
|
// LC-112 (FZ-8665 Round 45): each icon trigger's panel covers its button with
|
|
195
412
|
// no X, and a press outside closes it. The join panel's title sits on the
|
|
196
413
|
// panel's padding.
|
|
@@ -209,7 +426,7 @@ function AddNode() {
|
|
|
209
426
|
</Popover.Header>
|
|
210
427
|
<YStack paddingHorizontal="$4" gap="$3">
|
|
211
428
|
<Paragraph color="$color11">Run this on the new machine.</Paragraph>
|
|
212
|
-
<
|
|
429
|
+
<CodeBlock language="text">shc node join</CodeBlock>
|
|
213
430
|
</YStack>
|
|
214
431
|
</YStack>
|
|
215
432
|
</PopoverContent>
|
|
@@ -258,26 +475,9 @@ function NodesScreen() {
|
|
|
258
475
|
}
|
|
259
476
|
>
|
|
260
477
|
<PageHeader title="Nodes" divider={false} actions={<AddNode />} />
|
|
261
|
-
<
|
|
262
|
-
<
|
|
263
|
-
|
|
264
|
-
</SizableText>
|
|
265
|
-
<IconButton inline icon={CopyIcon} aria-label="Copy" />
|
|
266
|
-
</XStack>
|
|
267
|
-
<Card gap="$4">
|
|
268
|
-
<XStack justifyContent="space-between" alignItems="center">
|
|
269
|
-
<SizableText size="$6" fontWeight="600">
|
|
270
|
-
incus01
|
|
271
|
-
</SizableText>
|
|
272
|
-
<XStack alignItems="center">
|
|
273
|
-
<IconButton icon={DotsThreeIcon} aria-label="More for incus01" />
|
|
274
|
-
<IconButton error icon={TrashIcon} aria-label="Remove incus01" />
|
|
275
|
-
</XStack>
|
|
276
|
-
</XStack>
|
|
277
|
-
<SizableText color="$color11">10.64.96.1:4003</SizableText>
|
|
278
|
-
<Meter label="CPU" value={12} detail="2.83/24 cores 12%" />
|
|
279
|
-
<Meter label="Mem" value={41} detail="25.3/61.9 GiB 41%" />
|
|
280
|
-
</Card>
|
|
478
|
+
<PageSection title="Unplaced" description="1 node" headingLevel={3}>
|
|
479
|
+
<NodeFaceplate />
|
|
480
|
+
</PageSection>
|
|
281
481
|
</Screen>
|
|
282
482
|
);
|
|
283
483
|
}
|
|
@@ -315,3 +315,93 @@ export function SettingsPageLayout({
|
|
|
315
315
|
</Screen>
|
|
316
316
|
);
|
|
317
317
|
}
|
|
318
|
+
|
|
319
|
+
// ── DensePageLayout ────────────────────────────────────────────────────────
|
|
320
|
+
|
|
321
|
+
export interface DensePageLayoutProps
|
|
322
|
+
extends Omit<ScreenProps, "scroll" | "bleed" | "noPadding">, TemplateHeaderProps {
|
|
323
|
+
/** The live state beside the title (a `StatusDot`). */
|
|
324
|
+
status?: ReactNode;
|
|
325
|
+
/**
|
|
326
|
+
* The column the dense region keeps at regular width: `"wide"` (a log or
|
|
327
|
+
* terminal, the page column) or `"readable"` (a chart, the `expanded`
|
|
328
|
+
* measure, so a dozen bars never stretch across a desktop).
|
|
329
|
+
*/
|
|
330
|
+
measure?: "wide" | "readable";
|
|
331
|
+
/** Names the dense region (`data-dense-surface`), e.g. "log" or "chart". */
|
|
332
|
+
kind?: string;
|
|
333
|
+
/**
|
|
334
|
+
* Keep the page gutter inside the region on a phone. A chart takes it so its
|
|
335
|
+
* axis labels sit on the page line; a `LogView` pads its own text.
|
|
336
|
+
*/
|
|
337
|
+
gutter?: boolean;
|
|
338
|
+
children: ReactNode;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* A page whose content is one dense surface: a log, a terminal or a chart
|
|
343
|
+
* (LC-215). The page keeps its head (title, live status, icon actions) and
|
|
344
|
+
* the surface takes the rest of the height. On a phone the surface bleeds to
|
|
345
|
+
* both screen edges; from `medium` up it stays in the page column, rounded
|
|
346
|
+
* as the page's one surface, at the measure its content reads at.
|
|
347
|
+
*/
|
|
348
|
+
export function DensePageLayout({
|
|
349
|
+
title,
|
|
350
|
+
subtitle,
|
|
351
|
+
status,
|
|
352
|
+
actions,
|
|
353
|
+
header,
|
|
354
|
+
nav,
|
|
355
|
+
measure = "wide",
|
|
356
|
+
kind,
|
|
357
|
+
gutter = false,
|
|
358
|
+
maxWidth,
|
|
359
|
+
children,
|
|
360
|
+
...rest
|
|
361
|
+
}: DensePageLayoutProps) {
|
|
362
|
+
const compact = useLayoutSizeClass() === "compact";
|
|
363
|
+
const { knobProps } = useResolvedKnobs({ component: "Screen" });
|
|
364
|
+
const head =
|
|
365
|
+
header !== undefined ? (
|
|
366
|
+
header
|
|
367
|
+
) : title || subtitle || actions || nav || status ? (
|
|
368
|
+
<PageHeader title={title} subtitle={subtitle} status={status} actions={actions} nav={nav} />
|
|
369
|
+
) : null;
|
|
370
|
+
const region = (
|
|
371
|
+
<YStack
|
|
372
|
+
flex={1}
|
|
373
|
+
minHeight={0}
|
|
374
|
+
minWidth={0}
|
|
375
|
+
data-dense-surface={kind}
|
|
376
|
+
{...(compact
|
|
377
|
+
? gutter
|
|
378
|
+
? { paddingHorizontal: knobProps.panelPadding.padding as "$4" }
|
|
379
|
+
: null
|
|
380
|
+
: { ...knobProps.containerRadius, overflow: "hidden" })}
|
|
381
|
+
>
|
|
382
|
+
{children}
|
|
383
|
+
</YStack>
|
|
384
|
+
);
|
|
385
|
+
if (compact)
|
|
386
|
+
return (
|
|
387
|
+
<Screen data-canonical="dense-page" scroll={false} bleed noPadding {...rest}>
|
|
388
|
+
{head ? (
|
|
389
|
+
<YStack {...knobProps.panelPadding} paddingBottom={0}>
|
|
390
|
+
{head}
|
|
391
|
+
</YStack>
|
|
392
|
+
) : null}
|
|
393
|
+
{region}
|
|
394
|
+
</Screen>
|
|
395
|
+
);
|
|
396
|
+
return (
|
|
397
|
+
<Screen
|
|
398
|
+
data-canonical="dense-page"
|
|
399
|
+
scroll={false}
|
|
400
|
+
maxWidth={maxWidth ?? (measure === "readable" ? layoutBreakpoints.expanded : undefined)}
|
|
401
|
+
{...rest}
|
|
402
|
+
>
|
|
403
|
+
{head}
|
|
404
|
+
{region}
|
|
405
|
+
</Screen>
|
|
406
|
+
);
|
|
407
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LC-157 (FZ-8442): the bar's inline title is centred on the bar while it
|
|
3
|
+
* fits clear of both side groups, and slides into the span between them when
|
|
4
|
+
* the wider group would touch it (Clay's "Inbox" beside four trailing glyphs).
|
|
5
|
+
*/
|
|
6
|
+
import { describe, expect, it } from "vitest";
|
|
7
|
+
import { BAR_TITLE_GAP, barTitleSpan, sideExtents } from "./barTitleSpan";
|
|
8
|
+
|
|
9
|
+
describe("bar title span (LC-157, FZ-8442)", () => {
|
|
10
|
+
it("centres on the bar when the title fits clear of both groups", () => {
|
|
11
|
+
const span = barTitleSpan({ row: 358, leftEnd: 44, rightStart: 314, title: 60 });
|
|
12
|
+
expect(span.left).toBe(span.right);
|
|
13
|
+
expect(span.left).toBe(44 + BAR_TITLE_GAP);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("slides between the groups when centring would hide it", () => {
|
|
17
|
+
const span = barTitleSpan({ row: 358, leftEnd: 44, rightStart: 130, title: 45 });
|
|
18
|
+
expect(span).toEqual({ left: 44 + BAR_TITLE_GAP, right: 358 - 130 + BAR_TITLE_GAP });
|
|
19
|
+
expect(358 - span.left - span.right).toBeGreaterThanOrEqual(45);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("reads the groups by side, so a right-to-left bar mirrors", () => {
|
|
23
|
+
const ltr = sideExtents(358, { x: 16, width: 44 }, { x: 250, width: 92 });
|
|
24
|
+
const rtl = sideExtents(358, { x: 298, width: 44 }, { x: 16, width: 92 });
|
|
25
|
+
expect(ltr).toEqual({ leftEnd: 60, rightStart: 250 });
|
|
26
|
+
expect(rtl).toEqual({ leftEnd: 108, rightStart: 298 });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("an empty group takes no room", () => {
|
|
30
|
+
expect(sideExtents(358, { x: 16, width: 44 }, { x: 342, width: 0 })).toEqual({
|
|
31
|
+
leftEnd: 60,
|
|
32
|
+
rightStart: 0,
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** The space kept between the bar's inline title and either side group. */
|
|
2
|
+
export const BAR_TITLE_GAP = 8;
|
|
3
|
+
|
|
4
|
+
export interface BarTitleBox {
|
|
5
|
+
/** The bar row's width. */
|
|
6
|
+
row: number;
|
|
7
|
+
/** Where the group on the left ends, from the row's left edge; 0 when empty. */
|
|
8
|
+
leftEnd: number;
|
|
9
|
+
/** Where the group on the right starts, from the row's left edge; 0 when empty. */
|
|
10
|
+
rightStart: number;
|
|
11
|
+
/** The title's natural one-line width. */
|
|
12
|
+
title: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Where the bar's inline title sits. Like UIKit it is centred on the bar
|
|
17
|
+
* while it fits clear of both side groups; when the wider group would touch
|
|
18
|
+
* it, it centres in the span left between the groups instead of vanishing.
|
|
19
|
+
*/
|
|
20
|
+
export function barTitleSpan(box: BarTitleBox): { left: number; right: number } {
|
|
21
|
+
const left = box.leftEnd + BAR_TITLE_GAP;
|
|
22
|
+
const right = (box.rightStart ? box.row - box.rightStart : 0) + BAR_TITLE_GAP;
|
|
23
|
+
const centred = Math.max(left, right);
|
|
24
|
+
return box.row - 2 * centred >= box.title ? { left: centred, right: centred } : { left, right };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type Extent = { x: number; width: number };
|
|
28
|
+
|
|
29
|
+
/** The bar's two side groups as physical left and right extents (RTL-safe). */
|
|
30
|
+
export function sideExtents(
|
|
31
|
+
row: number,
|
|
32
|
+
a: Extent,
|
|
33
|
+
b: Extent,
|
|
34
|
+
): Pick<BarTitleBox, "leftEnd" | "rightStart"> {
|
|
35
|
+
const groups = [a, b].filter((group) => group.width > 0);
|
|
36
|
+
let leftEnd = 0;
|
|
37
|
+
let rightStart = 0;
|
|
38
|
+
for (const group of groups) {
|
|
39
|
+
if (group.x + group.width / 2 < row / 2)
|
|
40
|
+
leftEnd = Math.max(leftEnd, Math.ceil(group.x + group.width));
|
|
41
|
+
else rightStart = rightStart ? Math.min(rightStart, Math.floor(group.x)) : Math.floor(group.x);
|
|
42
|
+
}
|
|
43
|
+
return { leftEnd, rightStart };
|
|
44
|
+
}
|