@umriss-ui/dashboard 0.45.0 → 0.47.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/index.d.ts CHANGED
@@ -55,6 +55,11 @@ export declare interface DashboardProps extends HTMLAttributes<HTMLDivElement>,
55
55
  /** The `name`s of the widget types the catalogue lists.
56
56
  @default every declared type */
57
57
  catalogue?: ReadonlyArray<string>;
58
+ /** The level of the heading the heading widgets stand in - the one that fits the
59
+ page's outline. A widget's title stands one level below it when the dashboard
60
+ holds a heading widget, and at this level when not; never deeper than 6.
61
+ @default 2 */
62
+ headingLevel?: 2 | 3 | 4 | 5 | 6;
58
63
  }
59
64
 
60
65
  /** The arrangement of a dashboard: which widgets, in what order, at which
@@ -74,6 +79,9 @@ export declare interface DashboardWidget {
74
79
  size: WidgetSize;
75
80
  /** The type's own options, typed through the hook. */
76
81
  options: unknown;
82
+ /** Set by the application, never by the dashboard: the reader can move the widget but not
83
+ resize it, change its options or remove it. Only `true` locks. Kept through every edit. */
84
+ locked?: boolean;
77
85
  }
78
86
 
79
87
  /** The options of the heading widget type. */
@@ -135,7 +143,7 @@ export declare interface WidgetTypeProps<Options> {
135
143
  sizes: Partial<Record<WidgetSize, SizeSpan>>;
136
144
  /** The size a new widget of this type starts at. One of `sizes`. */
137
145
  start: WidgetSize;
138
- /** The options a new widget starts with. Their type is the options type of `title` and of the content. */
146
+ /** The options a new widget starts with, and the ones a stored widget's missing keys are read from: stored plain options are spread over them, one level deep. Their type is the options type of `title` and of the content. */
139
147
  defaultOptions: Options;
140
148
  /** The title of a frame as a function of its options; the `label` without it. */
141
149
  title?: (options: NoInfer<Options>) => string;
package/docs/llms-full.md CHANGED
@@ -1,4 +1,4 @@
1
- # @umriss-ui/dashboard 0.45.0
1
+ # @umriss-ui/dashboard 0.47.0
2
2
 
3
3
  > React dashboard for data-dense applications - widgets a reader arranges, packed in order on twelve columns.
4
4
 
@@ -277,7 +277,7 @@ function Stage({ shown }: { shown: Case }) {
277
277
  </Button>,
278
278
  ],
279
279
  governs: [
280
- <Button key="old" onClick={() => { parts.cancel(); parts.setView(OLD); setSaid("The stored value named a weather widget that is gone and a trend in a size it no longer offers: the one fell out, the other stands at its starting size, and the room beside it is free. Nothing broke."); }}>Load an old arrangement</Button>,
280
+ <Button key="old" onClick={() => { parts.cancel(); parts.setView(OLD); setSaid("The stored value named a weather widget whose type is not declared and a trend in a size it no longer offers: the weather widget is not drawn and stays in the stored value, the trend stands at its starting size, and the room beside it is free. Nothing broke."); }}>Load an old arrangement</Button>,
281
281
  <Button key="crash" onClick={() => { setBroken(true); setSaid("A trend threw while drawing. Its frame, head and menu stand; everything else keeps working."); }}>Let a widget fail</Button>,
282
282
  <Button key="reset" onClick={() => { parts.edit(); parts.reset(); setSaid("Back to the application's arrangement for this role - one more step of the draft: Undo brings yours back."); }} disabled={locked}>Reset to the default</Button>,
283
283
  <Button key="lock" onClick={() => { parts.cancel(); setLocked(!locked); setSaid(locked ? "Editing is allowed." : "Without editing the dashboard is complete as it is: the same arrangement, no button, no mode."); }}>{locked ? "Allow editing" : "Lock editing"}</Button>,
@@ -318,7 +318,7 @@ function Stage({ shown }: { shown: Case }) {
318
318
  )}
319
319
  <div data-callout="4" style={{ overflowX: "auto" }}>
320
320
  <div style={{ width: width ?? "100%" }}>
321
- <Dashboard aria-label="Dispatch, today">
321
+ <Dashboard headingLevel={3} aria-label="Dispatch, today">
322
322
  <HeadingType />
323
323
  <WidgetType
324
324
  name="figure"
@@ -578,7 +578,7 @@ export default function Dispatcher() {
578
578
  onViewChange: () => setKept((count) => count + 1),
579
579
  });
580
580
  return (
581
- <Dashboard aria-label="Dispatch, today" catalogue={["figure", "trend", "alerts"]} data-draft={open || undefined} data-kept={kept}>
581
+ <Dashboard headingLevel={4} aria-label="Dispatch, today" catalogue={["figure", "trend", "alerts"]} data-draft={open || undefined} data-kept={kept}>
582
582
  <HeadingType />
583
583
  <WidgetType
584
584
  name="figure"
@@ -671,7 +671,7 @@ export default function YourOwnWidget() {
671
671
  },
672
672
  });
673
673
  return (
674
- <Dashboard aria-label="Tours">
674
+ <Dashboard headingLevel={4} aria-label="Tours">
675
675
  <WidgetType
676
676
  name="tours"
677
677
  label="Tours on the road"
@@ -719,7 +719,7 @@ const VIEW: DashboardView = {
719
719
  export default function Sections() {
720
720
  const { Dashboard, WidgetType } = useDashboard({ defaultView: VIEW });
721
721
  return (
722
- <Dashboard aria-label="Sections">
722
+ <Dashboard headingLevel={4} aria-label="Sections">
723
723
  <HeadingType />
724
724
  <WidgetType name="note" label="Note" sentence="A line of text." sizes={{ wide: { columns: 6, rows: 3 } }} start="wide" defaultOptions={{}}>
725
725
  {() => <Text size="sm">Whatever the application puts in a widget.</Text>}
@@ -738,7 +738,294 @@ import { useDashboard } from "@umriss-ui/dashboard";
738
738
 
739
739
  export default function Empty() {
740
740
  const { Dashboard } = useDashboard({ defaultView: { widgets: [] } });
741
- return <Dashboard aria-label="Nothing yet" />;
741
+ return <Dashboard headingLevel={4} aria-label="Nothing yet" />;
742
+ }
743
+ ```
744
+
745
+ ##### A widget for one role
746
+
747
+ A widget type is declared only for the role that may see it. The other role does not draw the widget and does not lose it: the stored arrangement keeps it, and "Done" keeps it after the widgets that are drawn, so it comes back at the end.
748
+
749
+ ```tsx
750
+ import { useState } from "react";
751
+ import { Button, Text } from "@umriss-ui/core";
752
+ import { useDashboard } from "@umriss-ui/dashboard";
753
+ import type { DashboardView } from "@umriss-ui/dashboard";
754
+
755
+ type Role = "controller" | "dispatcher";
756
+
757
+ const VIEW: DashboardView = {
758
+ widgets: [
759
+ { id: "s1", type: "stock", size: "small", options: { name: "Pallets" } },
760
+ { id: "c1", type: "cost", size: "small", options: {} },
761
+ { id: "s2", type: "stock", size: "small", options: { name: "Drivers" } },
762
+ ],
763
+ };
764
+
765
+ export default function OneRole() {
766
+ const [role, setRole] = useState<Role>("controller");
767
+ const [told, setTold] = useState<string>();
768
+ const { Dashboard, WidgetType } = useDashboard({
769
+ defaultView: VIEW,
770
+ allowEdit: true,
771
+ onViewChange: (view) => setTold(view.widgets.map((widget) => `${widget.type} ${widget.id}`).join(", ")),
772
+ });
773
+ return (
774
+ <>
775
+ <Button variant={role === "controller" ? "primary" : "secondary"} onClick={() => setRole("controller")}>Controller</Button>{" "}
776
+ <Button variant={role === "dispatcher" ? "primary" : "secondary"} onClick={() => setRole("dispatcher")}>Dispatcher</Button>
777
+ <Dashboard headingLevel={4} aria-label="Dispatch, by role">
778
+ <WidgetType name="stock" label="Stock" sentence="A stock on hand." sizes={{ small: { columns: 4, rows: 2 } }} start="small" defaultOptions={{ name: "Pallets" }} title={(options) => options.name}>
779
+ {({ options }) => <Text size="sm">{`${options.name} on hand`}</Text>}
780
+ </WidgetType>
781
+ {role === "controller" && (
782
+ <WidgetType name="cost" label="Cost" sentence="What the week costs." sizes={{ small: { columns: 4, rows: 2 } }} start="small" defaultOptions={{}}>
783
+ {() => <Text size="sm">Cost this week: 4,820 EUR</Text>}
784
+ </WidgetType>
785
+ )}
786
+ </Dashboard>
787
+ <Text as="p" size="sm" tone="secondary">
788
+ {told ? `Told to the application, in this order: ${told}. ` : "The application was told nothing yet. "}
789
+ {"A widget of a type this role does not declare is not drawn, and \"Done\" keeps it after the drawn widgets, so it comes back at the end."}
790
+ </Text>
791
+ </>
792
+ );
793
+ }
794
+ ```
795
+
796
+ ##### An arrangement from an older release
797
+
798
+ The application keeps its own `{ version, view }` and migrates an older value with a plain function before the dashboard reads it. Three things need no migration of the application's: a type's new option takes its default, a size the type no longer offers falls back to its starting size, and a type that is gone stays in the stored view, not drawn.
799
+
800
+ ```tsx
801
+ import { useState } from "react";
802
+ import { Text } from "@umriss-ui/core";
803
+ import { useDashboard } from "@umriss-ui/dashboard";
804
+ import type { DashboardView, WidgetSize } from "@umriss-ui/dashboard";
805
+
806
+ /** What an older release stored: its own shape, with the figure's name as `label`. */
807
+ interface StoredV1 {
808
+ version: 1;
809
+ cards: ReadonlyArray<{ id: string; kind: string; size: WidgetSize; label?: string }>;
810
+ }
811
+
812
+ /** What the application keeps now. */
813
+ interface StoredV2 {
814
+ version: 2;
815
+ view: DashboardView;
816
+ }
817
+
818
+ /* The value as version 1 stored it: the trend is larger than it is offered now, the weather type is gone,
819
+ and no figure has the unit option that the type has since gained. */
820
+ const STORED: StoredV1 = {
821
+ version: 1,
822
+ cards: [
823
+ { id: "a", kind: "figure", size: "small", label: "Orders" },
824
+ { id: "b", kind: "trend", size: "large" },
825
+ { id: "c", kind: "weather", size: "small" },
826
+ { id: "d", kind: "figure", size: "medium", label: "Utilisation" },
827
+ ],
828
+ };
829
+
830
+ /** One older value in, the current one out. Nothing else needs to change. */
831
+ function migrate(stored: StoredV1 | StoredV2): StoredV2 {
832
+ if (stored.version === 2) return stored;
833
+ return {
834
+ version: 2,
835
+ view: { widgets: stored.cards.map((card) => ({ id: card.id, type: card.kind, size: card.size, options: card.label === undefined ? {} : { name: card.label } })) },
836
+ };
837
+ }
838
+
839
+ export default function OlderRelease() {
840
+ const [kept, setKept] = useState<StoredV2>(() => migrate(STORED));
841
+ const { Dashboard, WidgetType } = useDashboard({
842
+ defaultView: { widgets: [] },
843
+ initialView: kept.view,
844
+ onViewChange: (view) => setKept({ version: 2, view }),
845
+ });
846
+ return (
847
+ <>
848
+ <Dashboard headingLevel={4} aria-label="Orders and utilisation">
849
+ <WidgetType
850
+ name="figure"
851
+ label="Figure"
852
+ sentence="One number with its unit."
853
+ sizes={{ small: { columns: 3, rows: 2 }, medium: { columns: 4, rows: 2 } }}
854
+ start="small"
855
+ defaultOptions={{ name: "Orders", unit: "per day" }}
856
+ title={(options) => options.name}
857
+ >
858
+ {({ options }) => <Text size="sm">{`${options.name}, ${options.unit}`}</Text>}
859
+ </WidgetType>
860
+ <WidgetType name="trend" label="Trend" sentence="A figure over the last weeks." sizes={{ wide: { columns: 6, rows: 3 } }} start="wide" defaultOptions={{}}>
861
+ {() => <Text size="sm">Trend over the last weeks</Text>}
862
+ </WidgetType>
863
+ </Dashboard>
864
+ <Text as="p" size="sm" tone="secondary">
865
+ {`The application keeps version ${kept.version}: ${kept.view.widgets.map((widget) => widget.id).join(", ")}, in that order. A "Done" writes it back in version 2.`}
866
+ </Text>
867
+ </>
868
+ );
869
+ }
870
+ ```
871
+
872
+ ##### Controls of a widget's own
873
+
874
+ A widget's content can start with the application's own controls: a period switch above a chart that fills the rest of the body. The head takes no controls, because it is the grip, it takes the arrow keys and it carries the one menu. The row rests with the content in the editing mode, so arranging is never mistaken for using.
875
+
876
+ ```tsx
877
+ import { useState } from "react";
878
+ import { useChart } from "@umriss-ui/charts";
879
+ import { Button, ButtonGroup } from "@umriss-ui/core";
880
+ import { useDashboard } from "@umriss-ui/dashboard";
881
+
882
+ /* The values of each period: this week's days and the last month's weeks. */
883
+ const SERIES = {
884
+ week: [58, 52, 55, 51, 49, 53, 47],
885
+ month: [56, 58, 52, 55, 51, 49, 53, 47, 52, 56, 60, 57],
886
+ } as const;
887
+
888
+ type Period = keyof typeof SERIES;
889
+
890
+ /** A row of the application's controls at the top, the chart in the room left: the content is a column at the body's full height. */
891
+ function Orders() {
892
+ const [period, setPeriod] = useState<Period>("week");
893
+ const rows = SERIES[period].map((now, i) => ({ i, now }));
894
+ const { Chart, XAxis, YAxis, Area } = useChart(rows);
895
+ return (
896
+ <div style={{ display: "flex", flexDirection: "column", gap: "var(--u-space-2)", height: "100%", minWidth: 0 }}>
897
+ <div style={{ flex: "none" }}>
898
+ <ButtonGroup aria-label="Period" size="sm">
899
+ <Button variant={period === "week" ? "primary" : "secondary"} onClick={() => setPeriod("week")}>This week</Button>
900
+ <Button variant={period === "month" ? "primary" : "secondary"} onClick={() => setPeriod("month")}>Last month</Button>
901
+ </ButtonGroup>
902
+ </div>
903
+ <div style={{ flex: "1 1 0", minHeight: 0 }}>
904
+ <Chart aria-label={`Orders, ${period === "week" ? "this week" : "last month"}`} padding={4} style={{ minHeight: 0, height: "100%" }}>
905
+ <XAxis value="i" domain="data" grid={false} tickFormat={() => ""} />
906
+ <YAxis grid tickFormat={() => ""} />
907
+ <Area value="now" name="Orders" color="var(--u-color-accent)" fillOpacity={0.09} strokeWidth={1.75} />
908
+ </Chart>
909
+ </div>
910
+ </div>
911
+ );
912
+ }
913
+
914
+ export default function ControlsInAWidget() {
915
+ const { Dashboard, WidgetType } = useDashboard({
916
+ defaultView: { widgets: [{ id: "o1", type: "orders", size: "wide", options: {} }] },
917
+ });
918
+ return (
919
+ <Dashboard headingLevel={4} aria-label="Orders, by period">
920
+ <WidgetType
921
+ name="orders"
922
+ label="Orders by period"
923
+ sentence="The orders of the week or the month."
924
+ sizes={{ wide: { columns: 6, rows: 4 }, full: { columns: 12, rows: 4 } }}
925
+ start="wide"
926
+ defaultOptions={{}}
927
+ >
928
+ {() => <Orders />}
929
+ </WidgetType>
930
+ </Dashboard>
931
+ );
932
+ }
933
+ ```
934
+
935
+ ##### A widget every reader keeps
936
+
937
+ The application marks a widget of the default view as locked. In the editing mode the reader moves it like any other, but cannot remove it, resize it or change its options, and its menu says why. A lock lives in the view, so an arrangement a reader kept before the lock lacks it. The library does not merge the lock in: only the application knows whether a widget a kept view lacks was removed before the lock or must come back. Its function below sets the lock and puts a missing widget back at the start. A size the reader chose before the lock stays, since a lock keeps a size and does not choose one.
938
+
939
+ ```tsx
940
+ import { useState } from "react";
941
+ import { FormField, Select, Stack, Text } from "@umriss-ui/core";
942
+ import { useDashboard } from "@umriss-ui/dashboard";
943
+ import type { DashboardView, WidgetSize } from "@umriss-ui/dashboard";
944
+
945
+ const SUMMARY = "safety";
946
+
947
+ /* The figure type, declared once: a summary the kept view lacks starts with the type's starting size and default options. */
948
+ const FIGURE_DEFAULTS = { name: "Figure", value: "0", note: "" };
949
+ const FIGURE = { start: "small" as WidgetSize, defaultOptions: FIGURE_DEFAULTS };
950
+
951
+ const VIEW: DashboardView = {
952
+ widgets: [
953
+ { id: SUMMARY, type: "figure", size: "wide", options: { name: "Safety", value: "0 incidents", note: "Last 30 days" }, locked: true },
954
+ { id: "orders", type: "figure", size: "small", options: { name: "Orders", value: "128", note: "Today" } },
955
+ { id: "late", type: "figure", size: "small", options: { name: "Late tours", value: "3", note: "Of 41" } },
956
+ ],
957
+ };
958
+
959
+ /* Kept before the lock: the summary is there, unlocked, at a small size, between the other two. */
960
+ const KEPT_BEFORE_LOCK: DashboardView = {
961
+ widgets: [
962
+ { id: "orders", type: "figure", size: "small", options: { name: "Orders", value: "128", note: "Today" } },
963
+ { id: SUMMARY, type: "figure", size: "small", options: { name: "Safety", value: "0 incidents", note: "Last 30 days" } },
964
+ { id: "late", type: "figure", size: "small", options: { name: "Late tours", value: "3", note: "Of 41" } },
965
+ ],
966
+ };
967
+
968
+ /* Kept without the summary at all: it was removed before the lock. */
969
+ const KEPT_WITHOUT_SUMMARY: DashboardView = {
970
+ widgets: [
971
+ { id: "orders", type: "figure", size: "small", options: { name: "Orders", value: "128", note: "Today" } },
972
+ { id: "late", type: "figure", size: "small", options: { name: "Late tours", value: "3", note: "Of 41" } },
973
+ ],
974
+ };
975
+
976
+ const KEPT = { "The default view": VIEW, "Kept before the lock": KEPT_BEFORE_LOCK, "Kept without the summary": KEPT_WITHOUT_SUMMARY };
977
+ type Kept = keyof typeof KEPT;
978
+
979
+ /* The application's function, run before `initialView`: the summary is locked wherever the kept view has it, and a view that lacks it gets it back at the start. */
980
+ function withLock(kept: DashboardView): DashboardView {
981
+ const widgets = kept.widgets.map((widget) => (widget.id === SUMMARY ? { ...widget, locked: true } : widget));
982
+ if (widgets.some((widget) => widget.id === SUMMARY)) return { widgets };
983
+ return { widgets: [{ id: SUMMARY, type: "figure", size: FIGURE.start, options: FIGURE.defaultOptions, locked: true }, ...widgets] };
984
+ }
985
+
986
+ /* A new dashboard per kept arrangement: `initialView` is read once, so the choice remounts it. */
987
+ function Start({ kept }: { kept: DashboardView }) {
988
+ const { Dashboard, WidgetType, view } = useDashboard({ defaultView: VIEW, initialView: withLock(kept) });
989
+ const summary = view.widgets.find((widget) => widget.id === SUMMARY);
990
+ return (
991
+ <Stack gap={2}>
992
+ <Dashboard aria-label="Start" headingLevel={4}>
993
+ <WidgetType
994
+ name="figure"
995
+ label="Figure"
996
+ sentence="One number with a note."
997
+ sizes={{ small: { columns: 3, rows: 2 }, wide: { columns: 6, rows: 2 } }}
998
+ start={FIGURE.start}
999
+ defaultOptions={FIGURE.defaultOptions}
1000
+ title={(options: { name: string }) => options.name}
1001
+ >
1002
+ {({ options }: { options: { value: string; note: string } }) => (
1003
+ <Stack gap={1}>
1004
+ <Text as="div" size="2xl" weight="semibold">{options.value}</Text>
1005
+ <Text size="sm" tone="secondary">{options.note}</Text>
1006
+ </Stack>
1007
+ )}
1008
+ </WidgetType>
1009
+ </Dashboard>
1010
+ <Text size="sm" tone="secondary">{summary?.locked ? "The summary is locked." : "The summary is not locked."}</Text>
1011
+ </Stack>
1012
+ );
1013
+ }
1014
+
1015
+ export default function Locked() {
1016
+ const [choice, setChoice] = useState<Kept>("The default view");
1017
+ return (
1018
+ <Stack gap={4}>
1019
+ <FormField label="The arrangement the reader kept">
1020
+ <Select value={choice} onChange={(event) => setChoice(event.target.value as Kept)}>
1021
+ {(Object.keys(KEPT) as Kept[]).map((name) => (
1022
+ <option key={name}>{name}</option>
1023
+ ))}
1024
+ </Select>
1025
+ </FormField>
1026
+ <Start key={choice} kept={KEPT[choice]} />
1027
+ </Stack>
1028
+ );
742
1029
  }
743
1030
  ```
744
1031
 
@@ -766,24 +1053,24 @@ Every move, size, removal, addition, undo, redo, reset, "Done" and "Cancel" is a
766
1053
 
767
1054
  | Prop | Type | Default | Description |
768
1055
  |---|---|---|---|
769
- | `defaultView` *required* | `DashboardView` | — | The arrangement the application gives a reader who has none of their own.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 2 more |
770
- | `initialView` | `DashboardView` | the `defaultView` | The reader's kept arrangement, read once when the hook first runs; a different value on a later render is ignored, as React ignores a changed `defaultValue`. To go to another, call `setView`. |
771
- | `allowEdit` | `boolean` | `true` | Whether the reader may edit. Without it no bar is drawn and the mode cannot be entered.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Arrange the start screen (Scenarios)](#arrange-the-start-screen) |
1056
+ | `defaultView` *required* | `DashboardView` | — | The arrangement the application gives a reader who has none of their own.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 6 more |
1057
+ | `initialView` | `DashboardView` | the `defaultView` | The reader's kept arrangement, read once when the hook first runs; a different value on a later render is ignored, as React ignores a changed `defaultValue`. To go to another, call `setView`.<br>Shown in: [An arrangement from an older release](#dashboard), [A widget every reader keeps](#dashboard) |
1058
+ | `allowEdit` | `boolean` | `true` | Whether the reader may edit. Without it no bar is drawn and the mode cannot be entered.<br>Shown in: [The start screen of a dispatcher](#dashboard), [A widget for one role](#dashboard), [Arrange the start screen (Scenarios)](#arrange-the-start-screen) |
772
1059
 
773
1060
  ###### Events
774
1061
 
775
1062
  | Prop | Type | Default | Description |
776
1063
  |---|---|---|---|
777
- | `onViewChange` | `(view: ``DashboardView``) => void` | — | Called once per "Done" that changed something, with the whole arrangement. Not called by `setView`.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Arrange the start screen (Scenarios)](#arrange-the-start-screen) |
1064
+ | `onViewChange` | `(view: ``DashboardView``) => void` | — | Called once per "Done" that changed something, with the whole arrangement. Not called by `setView`.<br>Shown in: [The start screen of a dispatcher](#dashboard), [A widget for one role](#dashboard), [An arrangement from an older release](#dashboard) and 1 more |
778
1065
  | `onDraftChange` | `(open: boolean) => void` | — | Called with `true` when a draft opens and with `false` when it closes - to warn before navigation.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Arrange the start screen (Scenarios)](#arrange-the-start-screen) |
779
1066
 
780
1067
  ##### `DashboardParts`
781
1068
 
782
1069
  | Prop | Type | Default | Description |
783
1070
  |---|---|---|---|
784
- | `Dashboard` *required* | `(props: ``DashboardProps``) => ReactNode` | — | The dashboard. One per `useDashboard`.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 2 more |
785
- | `WidgetType` *required* | `typeof WidgetType` | — | Declares a widget type inside `Dashboard`.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 1 more |
786
- | `view` *required* | `DashboardView` | — | The arrangement in view: the draft while the mode is on. |
1071
+ | `Dashboard` *required* | `(props: ``DashboardProps``) => ReactNode` | — | The dashboard. One per `useDashboard`.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 6 more |
1072
+ | `WidgetType` *required* | `typeof WidgetType` | — | Declares a widget type inside `Dashboard`.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 5 more |
1073
+ | `view` *required* | `DashboardView` | — | The arrangement in view: the draft while the mode is on.<br>Shown in: [A widget every reader keeps](#dashboard) |
787
1074
  | `setView` *required* | `(view: ``DashboardView``) => void` | — | Goes to a whole arrangement. Not reported.<br>Shown in: [Arrange the start screen (Scenarios)](#arrange-the-start-screen) |
788
1075
  | `editing` *required* | `boolean` | — | Whether the editing mode is on.<br>Shown in: [Arrange the start screen (Scenarios)](#arrange-the-start-screen) |
789
1076
  | `edit` *required* | `() => void` | — | Enters the editing mode. Does nothing where editing is not allowed.<br>Shown in: [Arrange the start screen (Scenarios)](#arrange-the-start-screen) |
@@ -798,8 +1085,9 @@ Every move, size, removal, addition, undo, redo, reset, "Done" and "Cancel" is a
798
1085
 
799
1086
  | Prop | Type | Default | Description |
800
1087
  |---|---|---|---|
801
- | `children` | `ReactNode` | — | The `WidgetType`s of this dashboard.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 1 more |
1088
+ | `children` | `ReactNode` | — | The `WidgetType`s of this dashboard.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 5 more |
802
1089
  | `catalogue` | `ReadonlyArray<string>` | every declared type | The `name`s of the widget types the catalogue lists.<br>Shown in: [The start screen of a dispatcher](#dashboard) |
1090
+ | `headingLevel` | `2 \| 3 \| 4 \| 5 \| 6` | `2` | The level of the heading the heading widgets stand in - the one that fits the page's outline. A widget's title stands one level below it when the dashboard holds a heading widget, and at this level when not; never deeper than 6.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 6 more |
803
1091
  | `ref` | `Ref<HTMLDivElement> \| undefined` | — | Receives the component's root element. From `RefProp`. |
804
1092
 
805
1093
  Also takes every attribute of `<div>`.
@@ -808,16 +1096,17 @@ Also takes every attribute of `<div>`.
808
1096
 
809
1097
  | Prop | Type | Default | Description |
810
1098
  |---|---|---|---|
811
- | `widgets` *required* | `ReadonlyArray<``DashboardWidget``>` | — | The widgets, in the order they are drawn and read.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 2 more |
1099
+ | `widgets` *required* | `ReadonlyArray<``DashboardWidget``>` | — | The widgets, in the order they are drawn and read.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 6 more |
812
1100
 
813
1101
  ##### `DashboardWidget`
814
1102
 
815
1103
  | Prop | Type | Default | Description |
816
1104
  |---|---|---|---|
817
- | `id` *required* | `string` | — | Unique within the view.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 1 more |
818
- | `type` *required* | `string` | — | The `name` of the widget type it is an instance of.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 1 more |
819
- | `size` *required* | `WidgetSize`<br>`"small" \| "medium" \| "wide" \| "tall" \| "large" \| "full"` | — | One of the sizes its type offers.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 1 more |
820
- | `options` *required* | `unknown` | — | The type's own options, typed through the hook.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 1 more |
1105
+ | `id` *required* | `string` | — | Unique within the view.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 5 more |
1106
+ | `type` *required* | `string` | — | The `name` of the widget type it is an instance of.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 5 more |
1107
+ | `size` *required* | `WidgetSize`<br>`"small" \| "medium" \| "wide" \| "tall" \| "large" \| "full"` | — | One of the sizes its type offers.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 5 more |
1108
+ | `options` *required* | `unknown` | — | The type's own options, typed through the hook.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 5 more |
1109
+ | `locked` | `boolean` | — | Set by the application, never by the dashboard: the reader can move the widget but not resize it, change its options or remove it. Only `true` locks. Kept through every edit.<br>Shown in: [A widget every reader keeps](#dashboard) |
821
1110
 
822
1111
  ##### `WidgetSize`
823
1112
 
@@ -827,29 +1116,29 @@ Declares no props of its own.
827
1116
 
828
1117
  | Prop | Type | Default | Description |
829
1118
  |---|---|---|---|
830
- | `columns` *required* | `number` | — | Columns of twelve.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 1 more |
831
- | `rows` *required* | `number` | — | Rows of the fixed row unit.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 1 more |
1119
+ | `columns` *required* | `number` | — | Columns of twelve.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 5 more |
1120
+ | `rows` *required* | `number` | — | Rows of the fixed row unit.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 5 more |
832
1121
 
833
1122
  ##### `WidgetTypeProps<Options>`
834
1123
 
835
1124
  | Prop | Type | Default | Description |
836
1125
  |---|---|---|---|
837
- | `name` *required* | `string` | — | The name a view refers to this type by. Unique within the dashboard.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 1 more |
838
- | `label` *required* | `string` | — | The type's name for a reader, and the title of its frame unless `title` says otherwise.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 1 more |
839
- | `sentence` *required* | `string` | — | One sentence on what the type shows.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 1 more |
840
- | `sizes` *required* | `Partial<Record<``WidgetSize``, ``SizeSpan``>>` | — | The sizes the type offers, each with the columns (of twelve) and rows it takes.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 1 more |
841
- | `start` *required* | `WidgetSize`<br>`"small" \| "medium" \| "wide" \| "tall" \| "large" \| "full"` | — | The size a new widget of this type starts at. One of `sizes`.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 1 more |
842
- | `defaultOptions` *required* | `Options` | — | The options a new widget starts with. Their type is the options type of `title` and of the content.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 1 more |
843
- | `title` | `(options: NoInfer<Options>) => string` | — | The title of a frame as a function of its options; the `label` without it.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Arrange the start screen (Scenarios)](#arrange-the-start-screen) |
1126
+ | `name` *required* | `string` | — | The name a view refers to this type by. Unique within the dashboard.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 5 more |
1127
+ | `label` *required* | `string` | — | The type's name for a reader, and the title of its frame unless `title` says otherwise.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 5 more |
1128
+ | `sentence` *required* | `string` | — | One sentence on what the type shows.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 5 more |
1129
+ | `sizes` *required* | `Partial<Record<``WidgetSize``, ``SizeSpan``>>` | — | The sizes the type offers, each with the columns (of twelve) and rows it takes.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 5 more |
1130
+ | `start` *required* | `WidgetSize`<br>`"small" \| "medium" \| "wide" \| "tall" \| "large" \| "full"` | — | The size a new widget of this type starts at. One of `sizes`.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 5 more |
1131
+ | `defaultOptions` *required* | `Options` | — | The options a new widget starts with, and the ones a stored widget's missing keys are read from: stored plain options are spread over them, one level deep. Their type is the options type of `title` and of the content.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 5 more |
1132
+ | `title` | `(options: NoInfer<Options>) => string` | — | The title of a frame as a function of its options; the `label` without it.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [A widget for one role](#dashboard) and 3 more |
844
1133
  | `form` | `(props: ``WidgetFormProps``<NoInfer<Options>>) => ReactNode` | — | The form for the options, opened from the widget's menu. Without it the menu has no options entry.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Arrange the start screen (Scenarios)](#arrange-the-start-screen) |
845
- | `children` *required* | `(content: ``WidgetContent``<NoInfer<Options>>) => ReactNode` | — | The content, as a function of the size and the options.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 1 more |
1134
+ | `children` *required* | `(content: ``WidgetContent``<NoInfer<Options>>) => ReactNode` | — | The content, as a function of the size and the options.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Headings that divide the dashboard](#dashboard) and 5 more |
846
1135
 
847
1136
  ##### `WidgetContent<Options>`
848
1137
 
849
1138
  | Prop | Type | Default | Description |
850
1139
  |---|---|---|---|
851
1140
  | `size` *required* | `WidgetSize`<br>`"small" \| "medium" \| "wide" \| "tall" \| "large" \| "full"` | — | The size the widget stands at.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Arrange the start screen (Scenarios)](#arrange-the-start-screen) |
852
- | `options` *required* | `Options` | — | The widget's options.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [Arrange the start screen (Scenarios)](#arrange-the-start-screen) |
1141
+ | `options` *required* | `Options` | — | The widget's options.<br>Shown in: [The start screen of a dispatcher](#dashboard), [Your own component as a widget](#dashboard), [A widget for one role](#dashboard) and 2 more |
853
1142
 
854
1143
  ##### `WidgetFormProps<Options>`
855
1144
 
@@ -867,11 +1156,11 @@ Declares no props of its own.
867
1156
  #### Known limits
868
1157
 
869
1158
  - A widget's height follows its size, never its content: what does not fit is cut, not grown.
870
- - Not yet: sections that wrap as a whole, dragging from the catalogue to a chosen place, a height that follows the content in the list form, hiding a widget that has nothing to show, locking one widget ([ADR-0064](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0064-a-dashboard-a-user-arranges-is-a-sequence-on-fixed-columns.md)).
871
- - Never: stacks of widgets that share one place, coordinates and free placement, a layout per breakpoint, dense packing ([ADR-0064](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0064-a-dashboard-a-user-arranges-is-a-sequence-on-fixed-columns.md)).
1159
+ - Not yet: sections that wrap as a whole, a height that follows the content in the list form, hiding a widget that has nothing to show ([ADR-0064](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0064-a-dashboard-a-user-arranges-is-a-sequence-on-fixed-columns.md)).
1160
+ - Never: stacks of widgets that share one place, coordinates and free placement, a layout per breakpoint, dense packing, dragging from the catalogue to a chosen place ([ADR-0064](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0064-a-dashboard-a-user-arranges-is-a-sequence-on-fixed-columns.md)).
872
1161
  - Where an arrangement is stored, which default a role gets, several dashboards and what stands in a widget are the application's.
873
1162
 
874
- What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/dashboard-v0.45.0/docs/adr/0032-what-umriss-is-not.md).
1163
+ What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/dashboard-v0.47.0/docs/adr/0032-what-umriss-is-not.md).
875
1164
 
876
1165
  ## API index
877
1166
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umriss-ui/dashboard",
3
- "version": "0.45.0",
3
+ "version": "0.47.0",
4
4
  "description": "React dashboard for data-dense applications - widgets a reader arranges, packed in order on twelve columns.",
5
5
  "license": "MIT",
6
6
  "author": "Roman Händler",
@@ -54,7 +54,7 @@
54
54
  }
55
55
  },
56
56
  "peerDependencies": {
57
- "@umriss-ui/core": "^0.45.0",
57
+ "@umriss-ui/core": "^0.47.0",
58
58
  "react": ">=19",
59
59
  "react-dom": ">=19"
60
60
  },
@@ -70,7 +70,7 @@
70
70
  "typescript": "^5.6.3",
71
71
  "vite": "^6.0.0",
72
72
  "vite-plugin-dts": "^4.3.0",
73
- "@umriss-ui/core": "0.45.0",
73
+ "@umriss-ui/core": "0.47.0",
74
74
  "@umriss-ui/demo": "0.0.0"
75
75
  },
76
76
  "scripts": {