@gogitcms/design-system 0.16.0-next.12 → 0.16.0-next.13

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gogitcms/design-system",
3
- "version": "0.16.0-next.12",
3
+ "version": "0.16.0-next.13",
4
4
  "main": "src/index.ts",
5
5
  "types": "src/index.ts",
6
6
  "// exports": "The root entry is the react-native source the SPAs, desktop app and mobile app consume. ./web is the plain-DOM build for server-rendered surfaces (the Astro docs site) that don't run react-native-web, and ./css ships the tokens as custom properties. The trailing wildcard keeps deep paths resolvable — plugin bundling and the Vite aliases reach into src/ directly.",
@@ -68,6 +68,7 @@ const galleryField: EntryField = {
68
68
  type: "array",
69
69
  of: "string",
70
70
  component: "list",
71
+ itemComponent: "media",
71
72
  media: "uploads",
72
73
  storeAs: "public",
73
74
  value: ["/uploads/hero.png"],
@@ -89,7 +90,7 @@ test("array items render media pickers when the array names a media set", async
89
90
  });
90
91
 
91
92
  test("a list with no media set stays a plain list of inputs", () => {
92
- renderWith([{ ...galleryField, media: undefined, storeAs: undefined, value: ["x"] }]);
93
+ renderWith([{ ...galleryField, itemComponent: undefined, media: undefined, storeAs: undefined, value: ["x"] }]);
93
94
  expect(screen.queryByTestId("media-choose")).not.toBeInTheDocument();
94
95
  });
95
96
 
@@ -129,7 +129,7 @@ test("an object reference shows its embedded copies read-only and picks by key c
129
129
 
130
130
  test("an array of references renders a picker per item", async () => {
131
131
  renderWith([
132
- { name: "related", label: "Related", type: "array", of: "string", component: "list", reference: authorDef, value: [ada.key, bob.key] },
132
+ { name: "related", label: "Related", type: "array", of: "string", component: "list", itemComponent: "reference", reference: authorDef, value: [ada.key, bob.key] },
133
133
  ]);
134
134
  expect(await screen.findAllByTestId("reference-choose")).toHaveLength(2);
135
135
  expect(await screen.findByText("Bob")).toBeInTheDocument();
@@ -137,7 +137,7 @@ test("an array of references renders a picker per item", async () => {
137
137
 
138
138
  test("Add item on a reference list opens the picker and appends the pick", async () => {
139
139
  renderWith([
140
- { name: "related", label: "Related", type: "array", of: "string", component: "list", reference: authorDef, value: [ada.key] },
140
+ { name: "related", label: "Related", type: "array", of: "string", component: "list", itemComponent: "reference", reference: authorDef, value: [ada.key] },
141
141
  ]);
142
142
  expect(await screen.findByText("Ada Lovelace")).toBeInTheDocument();
143
143
 
@@ -153,7 +153,7 @@ test("Add item on a reference list opens the picker and appends the pick", async
153
153
 
154
154
  test("Add item on a reference list without a seam is disabled rather than adding a slot", async () => {
155
155
  renderWith(
156
- [{ name: "related", label: "Related", type: "array", of: "string", component: "list", reference: authorDef, value: [] }],
156
+ [{ name: "related", label: "Related", type: "array", of: "string", component: "list", itemComponent: "reference", reference: authorDef, value: [] }],
157
157
  null,
158
158
  );
159
159
  fireEvent.click(await screen.findByTestId("list-add"));
@@ -74,21 +74,26 @@ export type EntryField = {
74
74
  source?: string;
75
75
  format?: string;
76
76
  flavor?: string;
77
- // For a media picker (`component: "media"`): which configured media set to
78
- // browse, and — on a string field — which form of the path to write. `media`
79
- // comes from display.media, `storeAs` from display.store_as. On an array
80
- // field these describe its *items*, making each one a media picker.
77
+ // For a media picker (`component: "media"`, or an array whose items are —
78
+ // `itemComponent: "media"`): which configured media set to browse, and —
79
+ // for a string value — which form of the path to write. Both come from the
80
+ // field's `media:` block; on an array they describe its *items*.
81
81
  media?: string;
82
82
  storeAs?: string;
83
83
  // For a document picker (`component: "reference"`, or an array whose items
84
- // are references): what the field points at and stores. An object-shaped
85
- // reference's `fields` are the derived children — the key child and the
86
- // embedded copies — which the server recomputes on every save and the
87
- // editor shows read-only (docs/references.md).
84
+ // are — `itemComponent: "reference"`): what the field points at and stores.
85
+ // An object-shaped reference declares its `fields` — the key child and the
86
+ // embedded copies — and the copies are the server's: recomputed on every
87
+ // save and shown read-only here (docs/references.md).
88
88
  reference?: ReferenceDef;
89
89
  value: unknown;
90
90
  // Item type for an array field (string | number | boolean | object).
91
91
  of?: string;
92
+ // The control each item of an array renders, from display.item.component.
93
+ // `list` describes the array; what an item is — a text input, a media
94
+ // picker, a document picker — is declared here, never inferred from the
95
+ // array's media or reference block. Absent means the default for `of`.
96
+ itemComponent?: string;
92
97
  // Child fields for an object field (or an array whose `of` is object).
93
98
  fields?: EntryField[];
94
99
  // Object shapes a mixed-list array (component: "mixedList") can hold.
@@ -1715,8 +1720,10 @@ function ListControl({
1715
1720
  // what was picked: the only thing such an item can hold is a picked
1716
1721
  // document, so an empty slot to fill in afterwards is a step nobody wants.
1717
1722
  // Without a host seam there is nothing to pick from, and the button is
1718
- // disabled as the field's own Choose button is.
1719
- const reference = field.reference;
1723
+ // disabled as the field's own Choose button is. "Reference list" is the
1724
+ // item's declared control, not the presence of a block: an array whose
1725
+ // items are not declared pickers is a plain list whatever else it carries.
1726
+ const reference = field.itemComponent === "reference" ? field.reference : undefined;
1720
1727
  const referenceApi = useReferenceApi();
1721
1728
  const [pickingNew, setPickingNew] = React.useState(false);
1722
1729
  const addItem = () => {
@@ -1732,20 +1739,21 @@ function ListControl({
1732
1739
  onChange([...items, referenceValue(target, isObject ? "object" : "string", reference, undefined)]);
1733
1740
  };
1734
1741
 
1735
- // Items inherit the array's *item-level* display config, not the array's own
1736
- // component — `list` describes the array, and is not a control an item could
1737
- // render. An array that names a media set makes each item a media picker;
1738
- // everything else falls back to the default control for the item type.
1742
+ // Items render the array's *declared* item control (display.item.component),
1743
+ // not the array's own component — `list` describes the array, and is not a
1744
+ // control an item could render. The media and reference blocks describe the
1745
+ // items too, so they travel with the item; they never pick its control. An
1746
+ // array that declares nothing falls back to the default control for `of`.
1739
1747
  const itemField: EntryField = {
1740
1748
  name: field.name,
1741
1749
  type: itemType,
1742
1750
  of: undefined,
1743
1751
  fields: field.fields,
1744
1752
  value: null,
1745
- ...(field.media ? { component: "media", media: field.media, storeAs: field.storeAs } : {}),
1746
- // An array that declares a reference makes each item a document picker, the
1747
- // same way an array naming a media set makes each item a media picker.
1748
- ...(field.reference ? { component: "reference", reference: field.reference } : {}),
1753
+ component: field.itemComponent,
1754
+ media: field.media,
1755
+ storeAs: field.storeAs,
1756
+ reference: field.reference,
1749
1757
  };
1750
1758
 
1751
1759
  // The list's own path, from the FieldControl wrapping this control; an item
@@ -61,7 +61,7 @@ export type MediaFieldProps = {
61
61
  // object field and an empty string field look identical at runtime — and
62
62
  // guessing would write the wrong shape into the document on first pick.
63
63
  shape: "string" | "object";
64
- // The media set this field browses, from the config's display.media.
64
+ // The media set this field browses, from the config's media block.
65
65
  set?: string;
66
66
  // Which form a string field writes. Ignored for the object form, which stores
67
67
  // the asset id.
@@ -388,7 +388,7 @@ function AmbiguousNotice({
388
388
  // for an image without knowing anything about the picker UI.
389
389
  //
390
390
  // `set` defaults to the first configured set when the field names none (a body
391
- // field has no display.media), and the picker's own switcher covers the rest.
391
+ // field has no media block), and the picker's own switcher covers the rest.
392
392
  export function useFieldMedia(opts: { set?: string; storeAs?: string }): {
393
393
  media: FieldMediaApi | undefined;
394
394
  picker: React.ReactNode;
@@ -127,10 +127,16 @@ function NotificationRow({
127
127
  item,
128
128
  divided,
129
129
  onPress,
130
+ // How much of the body a row shows. Two lines suits the bell's menu, which is
131
+ // a glance at what happened; the full-screen list gives it more, because the
132
+ // body of an error notification is the error itself and a reader who opened
133
+ // the list to find out what went wrong has nowhere else to read it.
134
+ bodyLines = 2,
130
135
  }: {
131
136
  item: NotificationItem;
132
137
  divided?: boolean;
133
138
  onPress?: () => void;
139
+ bodyLines?: number;
134
140
  }) {
135
141
  const t = useTheme();
136
142
  const titleColor = item.level === "error" ? t.color.diffDelFg : t.color.textPrimary;
@@ -199,7 +205,9 @@ function NotificationRow({
199
205
  </View>
200
206
  <Text variant="monoSm" color="tertiary">{relativeTime(item.createdAt)}</Text>
201
207
  </View>
202
- {item.body ? <InlineCode text={item.body} variant="monoSm" color={t.color.textSecondary} numberOfLines={2} /> : null}
208
+ {item.body ? (
209
+ <InlineCode text={item.body} variant="monoSm" color={t.color.textSecondary} numberOfLines={bodyLines} />
210
+ ) : null}
203
211
  {meta ? (
204
212
  <Text variant="monoSm" color="tertiary" numberOfLines={1}>{meta}</Text>
205
213
  ) : null}
@@ -412,6 +420,7 @@ export function NotificationList({
412
420
  item={n}
413
421
  divided={i < notifications.length - 1}
414
422
  onPress={onPressItem ? () => onPressItem(n) : undefined}
423
+ bodyLines={8}
415
424
  />
416
425
  ))}
417
426
  </ScrollView>
package/src/references.ts CHANGED
@@ -17,7 +17,7 @@ export interface ReferenceDef {
17
17
  key: string;
18
18
  // For the object shape, the child that holds the key ("ref" unless renamed).
19
19
  keyName: string;
20
- // The embedded copies, in the order the derived children appear.
20
+ // The embedded copies, in the order their children are listed.
21
21
  embed: ReferenceEmbed[];
22
22
  // What deleting a referenced document does: restrict | unset.
23
23
  onDelete: string;