@groveback/ui 0.5.0 → 0.7.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/components.d.ts +19 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +161 -67
- package/dist/locale.d.ts +32 -0
- package/dist/messages.d.ts +2 -0
- package/dist/runtime.d.ts +18 -0
- package/package.json +1 -1
package/dist/components.d.ts
CHANGED
|
@@ -37,6 +37,8 @@ export declare function Stack(props: {
|
|
|
37
37
|
}): import("react").JSX.Element;
|
|
38
38
|
export declare function Grid(props: {
|
|
39
39
|
columns: number;
|
|
40
|
+
/** Columns below the breakpoint; defaults to one. */
|
|
41
|
+
columnsMobile?: number;
|
|
40
42
|
gap?: number;
|
|
41
43
|
width?: keyof typeof WIDTH;
|
|
42
44
|
children: ReactNode;
|
|
@@ -58,6 +60,23 @@ export declare function Text({ muted, children }: {
|
|
|
58
60
|
* `href` is still real, which is what keeps middle-click, "open in new tab" and crawlers
|
|
59
61
|
* working — the click handler only takes over the plain-left-click case.
|
|
60
62
|
*/
|
|
63
|
+
/**
|
|
64
|
+
* The control a viewer switches language with.
|
|
65
|
+
*
|
|
66
|
+
* Renders nothing unless the app actually offers a choice: fewer than two document locales, or
|
|
67
|
+
* no `setLocale` on the context (an app driving `locale` as a fixed prop owns it — see
|
|
68
|
+
* `GroveRoutes`). So a monolingual app looks exactly as it did before this existed.
|
|
69
|
+
*
|
|
70
|
+
* It offers EVERY locale the document declares, not the ones this package ships chrome for.
|
|
71
|
+
* The screens' own words are the substance of the page and are translated into all of them;
|
|
72
|
+
* hiding a language because the package has no "Save" in it would hide a page that is fully
|
|
73
|
+
* translated apart from one button. Chrome the catalogue lacks degrades to English, which is
|
|
74
|
+
* a word rather than a blank — and the `messages` override is the documented way out (D86).
|
|
75
|
+
*
|
|
76
|
+
* A native `<select>`: keyboard and screen-reader behaviour for free, and a phone renders its
|
|
77
|
+
* own picker instead of a menu that has to be re-implemented for touch.
|
|
78
|
+
*/
|
|
79
|
+
export declare function LocaleSwitcher(): import("react").JSX.Element | null;
|
|
61
80
|
export declare function Header(props: {
|
|
62
81
|
brand?: ReactNode;
|
|
63
82
|
brandHref?: string | undefined;
|
package/dist/index.d.ts
CHANGED
|
@@ -10,14 +10,15 @@
|
|
|
10
10
|
* convention — this package is browser React bundled by `bun build`, not backend ESM, so the
|
|
11
11
|
* convention buys nothing and extensionless is what every bundler agrees on.
|
|
12
12
|
*/
|
|
13
|
-
import { Button, DataTable, Detail, Divider, Form, Grid, Header, Heading, Stack, Text } from './components';
|
|
13
|
+
import { Button, DataTable, Detail, Divider, Form, Grid, Header, Heading, LocaleSwitcher, Stack, Text } from './components';
|
|
14
|
+
import { localeLabel } from './locale';
|
|
14
15
|
import { MarkdownField, renderMarkdown } from './MarkdownField';
|
|
15
16
|
import { RelationSelect } from './RelationSelect';
|
|
16
17
|
import { GroveRoutes, RouteIndex, matchRoute, resolveRoute } from './routes';
|
|
17
18
|
import { SignIn } from './SignIn';
|
|
18
19
|
import { uiMessagesFor } from './messages';
|
|
19
20
|
import { GroveUiProvider, detectLocale, formatValue, resolveHref, useAction, useGroveUi, useMessages, usePageTitle, useStandaloneUiText, useUiMessages, useUiText } from './runtime';
|
|
20
|
-
export { Button, DataTable, Detail, Divider, Form, Grid, GroveRoutes, GroveUiProvider, Header, Heading, MarkdownField, renderMarkdown, RelationSelect, RouteIndex, SignIn, Stack, Text, detectLocale, formatValue, matchRoute, resolveHref, resolveRoute, useAction, useGroveUi, useMessages, usePageTitle, useStandaloneUiText, uiMessagesFor, useUiMessages, useUiText, };
|
|
21
|
+
export { Button, DataTable, Detail, Divider, Form, Grid, GroveRoutes, GroveUiProvider, Header, Heading, LocaleSwitcher, MarkdownField, renderMarkdown, RelationSelect, RouteIndex, SignIn, Stack, Text, detectLocale, formatValue, localeLabel, matchRoute, resolveHref, resolveRoute, useAction, useGroveUi, useMessages, usePageTitle, useStandaloneUiText, uiMessagesFor, useUiMessages, useUiText, };
|
|
21
22
|
export type { RelationSelectProps } from './RelationSelect';
|
|
22
23
|
export type { GroveUiContext } from './runtime';
|
|
23
24
|
export type { UiMessageKey, UiMessages } from './messages';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
// src/components.tsx
|
|
2
2
|
import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
|
|
3
3
|
|
|
4
|
+
// src/locale.ts
|
|
5
|
+
var LOCALE_STORAGE_KEY = "grove.locale";
|
|
6
|
+
function readStoredLocale(available) {
|
|
7
|
+
if (typeof window === "undefined")
|
|
8
|
+
return;
|
|
9
|
+
let stored = null;
|
|
10
|
+
try {
|
|
11
|
+
stored = window.localStorage.getItem(LOCALE_STORAGE_KEY);
|
|
12
|
+
} catch {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (stored === null)
|
|
16
|
+
return;
|
|
17
|
+
return available.includes(stored) ? stored : undefined;
|
|
18
|
+
}
|
|
19
|
+
function storeLocale(locale) {
|
|
20
|
+
if (typeof window === "undefined")
|
|
21
|
+
return;
|
|
22
|
+
try {
|
|
23
|
+
window.localStorage.setItem(LOCALE_STORAGE_KEY, locale);
|
|
24
|
+
} catch {}
|
|
25
|
+
}
|
|
26
|
+
function localeLabel(locale) {
|
|
27
|
+
try {
|
|
28
|
+
const names = new Intl.DisplayNames([locale], { type: "language" });
|
|
29
|
+
const label = names.of(locale);
|
|
30
|
+
if (label && label.toLowerCase() !== locale.toLowerCase()) {
|
|
31
|
+
return label.charAt(0).toUpperCase() + label.slice(1);
|
|
32
|
+
}
|
|
33
|
+
} catch {}
|
|
34
|
+
return locale;
|
|
35
|
+
}
|
|
36
|
+
|
|
4
37
|
// src/MarkdownField.tsx
|
|
5
38
|
import { useState } from "react";
|
|
6
39
|
|
|
@@ -17,6 +50,7 @@ var EN = {
|
|
|
17
50
|
"common.no": "No",
|
|
18
51
|
"header.openMenu": "Open menu",
|
|
19
52
|
"header.closeMenu": "Close menu",
|
|
53
|
+
"locale.label": "Language",
|
|
20
54
|
"table.empty": "Nothing here yet.",
|
|
21
55
|
"form.save": "Save",
|
|
22
56
|
"form.saving": "Saving…",
|
|
@@ -62,6 +96,7 @@ var ES = {
|
|
|
62
96
|
"common.no": "No",
|
|
63
97
|
"header.openMenu": "Abrir menú",
|
|
64
98
|
"header.closeMenu": "Cerrar menú",
|
|
99
|
+
"locale.label": "Idioma",
|
|
65
100
|
"table.empty": "Todavía no hay nada.",
|
|
66
101
|
"form.save": "Guardar",
|
|
67
102
|
"form.saving": "Guardando…",
|
|
@@ -544,6 +579,7 @@ import { jsx as jsx4, jsxs as jsxs3, Fragment } from "react/jsx-runtime";
|
|
|
544
579
|
var WIDTH_CLASS = { narrow: "w-[12%]", normal: "", wide: "w-[45%]" };
|
|
545
580
|
var GAP = ["gap-0", "gap-1", "gap-2", "gap-3", "gap-4", "gap-5", "gap-6", "gap-7", "gap-8", "gap-9", "gap-10", "gap-11", "gap-12"];
|
|
546
581
|
var COLUMNS = ["", "md:grid-cols-1", "md:grid-cols-2", "md:grid-cols-3", "md:grid-cols-4", "md:grid-cols-5", "md:grid-cols-6"];
|
|
582
|
+
var BASE_COLUMNS = ["", "grid-cols-1", "grid-cols-2", "grid-cols-3", "grid-cols-4", "grid-cols-5", "grid-cols-6"];
|
|
547
583
|
var WIDTH = { full: "w-full", container: "w-full max-w-5xl mx-auto", narrow: "w-full max-w-xl mx-auto" };
|
|
548
584
|
var ALIGN = { start: "items-start", center: "items-center", end: "items-end", stretch: "items-stretch" };
|
|
549
585
|
var JUSTIFY = { start: "justify-start", center: "justify-center", end: "justify-end", between: "justify-between" };
|
|
@@ -555,9 +591,13 @@ function Stack(props) {
|
|
|
555
591
|
});
|
|
556
592
|
}
|
|
557
593
|
function Grid(props) {
|
|
558
|
-
const { columns, gap = 4, width, children } = props;
|
|
594
|
+
const { columns, columnsMobile, gap = 4, width, children } = props;
|
|
595
|
+
const mobile = Math.min(Math.max(columnsMobile ?? 1, 1), 6);
|
|
596
|
+
const desktop = Math.min(Math.max(columns, 1), 6);
|
|
559
597
|
return /* @__PURE__ */ jsx4("div", {
|
|
560
|
-
|
|
598
|
+
"data-grove-cols": desktop,
|
|
599
|
+
"data-grove-cols-mobile": mobile,
|
|
600
|
+
className: cx("grid", BASE_COLUMNS[mobile], COLUMNS[desktop], GAP[Math.min(Math.max(gap, 0), 12)], width && WIDTH[width]),
|
|
561
601
|
children
|
|
562
602
|
});
|
|
563
603
|
}
|
|
@@ -580,11 +620,30 @@ function Text({ muted, children }) {
|
|
|
580
620
|
children
|
|
581
621
|
});
|
|
582
622
|
}
|
|
623
|
+
function LocaleSwitcher() {
|
|
624
|
+
const { locale, locales, setLocale } = useGroveUi();
|
|
625
|
+
const t = useUiText();
|
|
626
|
+
if (!setLocale || !locales || locales.length < 2)
|
|
627
|
+
return null;
|
|
628
|
+
return /* @__PURE__ */ jsx4("select", {
|
|
629
|
+
"data-grove-locale": true,
|
|
630
|
+
"aria-label": t("locale.label"),
|
|
631
|
+
value: locale ?? locales[0],
|
|
632
|
+
onChange: (e) => setLocale(e.target.value),
|
|
633
|
+
className: cx(INPUT_CLASS, "py-1 pr-7 text-xs"),
|
|
634
|
+
children: locales.map((tag) => /* @__PURE__ */ jsx4("option", {
|
|
635
|
+
value: tag,
|
|
636
|
+
children: localeLabel(tag)
|
|
637
|
+
}, tag))
|
|
638
|
+
});
|
|
639
|
+
}
|
|
583
640
|
function Header(props) {
|
|
584
641
|
const { brand, brandHref = "/", links, actions, sticky } = props;
|
|
585
642
|
const dispatch = useAction();
|
|
586
643
|
const t = useUiText();
|
|
644
|
+
const { onHeaderMounted } = useGroveUi();
|
|
587
645
|
const [open, setOpen] = useState3(false);
|
|
646
|
+
useEffect3(() => onHeaderMounted?.(), [onHeaderMounted]);
|
|
588
647
|
const go = useCallback2((href) => (e) => {
|
|
589
648
|
if (e.defaultPrevented || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0)
|
|
590
649
|
return;
|
|
@@ -615,70 +674,76 @@ function Header(props) {
|
|
|
615
674
|
children: link.label
|
|
616
675
|
}, i))
|
|
617
676
|
}) : null,
|
|
618
|
-
|
|
619
|
-
"
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
678
|
+
className: "ml-auto flex items-center gap-2",
|
|
679
|
+
children: [
|
|
680
|
+
actions && actions.length > 0 ? /* @__PURE__ */ jsx4("div", {
|
|
681
|
+
"data-grove-nav-actions": true,
|
|
682
|
+
className: "hidden items-center gap-2 md:flex",
|
|
683
|
+
children: actions.map((a, i) => /* @__PURE__ */ jsx4(Button, {
|
|
684
|
+
action: a.action,
|
|
685
|
+
variant: a.variant,
|
|
686
|
+
children: a.label
|
|
687
|
+
}, i))
|
|
688
|
+
}) : null,
|
|
689
|
+
/* @__PURE__ */ jsx4(LocaleSwitcher, {}),
|
|
690
|
+
hasNav ? /* @__PURE__ */ jsx4("button", {
|
|
691
|
+
type: "button",
|
|
692
|
+
"data-grove-nav-toggle": true,
|
|
693
|
+
"aria-label": t(open ? "header.closeMenu" : "header.openMenu"),
|
|
694
|
+
"aria-expanded": open,
|
|
695
|
+
onClick: () => setOpen((v) => !v),
|
|
696
|
+
className: "inline-flex items-center justify-center rounded-md p-2 text-neutral-600 hover:bg-neutral-100 hover:text-neutral-900 md:hidden dark:text-neutral-400 dark:hover:bg-neutral-800 dark:hover:text-neutral-100",
|
|
697
|
+
children: /* @__PURE__ */ jsx4("svg", {
|
|
698
|
+
width: "20",
|
|
699
|
+
height: "20",
|
|
700
|
+
viewBox: "0 0 24 24",
|
|
701
|
+
fill: "none",
|
|
702
|
+
stroke: "currentColor",
|
|
703
|
+
strokeWidth: "2",
|
|
704
|
+
strokeLinecap: "round",
|
|
705
|
+
"aria-hidden": "true",
|
|
706
|
+
children: open ? /* @__PURE__ */ jsxs3(Fragment, {
|
|
707
|
+
children: [
|
|
708
|
+
/* @__PURE__ */ jsx4("line", {
|
|
709
|
+
x1: "18",
|
|
710
|
+
y1: "6",
|
|
711
|
+
x2: "6",
|
|
712
|
+
y2: "18"
|
|
713
|
+
}),
|
|
714
|
+
/* @__PURE__ */ jsx4("line", {
|
|
715
|
+
x1: "6",
|
|
716
|
+
y1: "6",
|
|
717
|
+
x2: "18",
|
|
718
|
+
y2: "18"
|
|
719
|
+
})
|
|
720
|
+
]
|
|
721
|
+
}) : /* @__PURE__ */ jsxs3(Fragment, {
|
|
722
|
+
children: [
|
|
723
|
+
/* @__PURE__ */ jsx4("line", {
|
|
724
|
+
x1: "3",
|
|
725
|
+
y1: "6",
|
|
726
|
+
x2: "21",
|
|
727
|
+
y2: "6"
|
|
728
|
+
}),
|
|
729
|
+
/* @__PURE__ */ jsx4("line", {
|
|
730
|
+
x1: "3",
|
|
731
|
+
y1: "12",
|
|
732
|
+
x2: "21",
|
|
733
|
+
y2: "12"
|
|
734
|
+
}),
|
|
735
|
+
/* @__PURE__ */ jsx4("line", {
|
|
736
|
+
x1: "3",
|
|
737
|
+
y1: "18",
|
|
738
|
+
x2: "21",
|
|
739
|
+
y2: "18"
|
|
740
|
+
})
|
|
741
|
+
]
|
|
677
742
|
})
|
|
678
|
-
|
|
679
|
-
})
|
|
680
|
-
|
|
681
|
-
})
|
|
743
|
+
})
|
|
744
|
+
}) : null
|
|
745
|
+
]
|
|
746
|
+
})
|
|
682
747
|
]
|
|
683
748
|
}),
|
|
684
749
|
hasNav && open ? /* @__PURE__ */ jsxs3("div", {
|
|
@@ -1111,6 +1176,13 @@ function resolveRoute(routes, path) {
|
|
|
1111
1176
|
function GroveRoutes(props) {
|
|
1112
1177
|
const { routes, context, fallback, children, gate } = props;
|
|
1113
1178
|
const [path, setPath] = useState4(() => window.location.pathname);
|
|
1179
|
+
const offered = context?.locales;
|
|
1180
|
+
const pinned = context?.locale;
|
|
1181
|
+
const [chosen, setChosen] = useState4(() => pinned !== undefined || !offered || offered.length === 0 ? undefined : readStoredLocale(offered) ?? detectLocale(offered));
|
|
1182
|
+
const setLocale = useCallback3((next) => {
|
|
1183
|
+
setChosen(next);
|
|
1184
|
+
storeLocale(next);
|
|
1185
|
+
}, []);
|
|
1114
1186
|
useEffect4(() => {
|
|
1115
1187
|
const onPop = () => setPath(window.location.pathname);
|
|
1116
1188
|
window.addEventListener("popstate", onPop);
|
|
@@ -1120,7 +1192,23 @@ function GroveRoutes(props) {
|
|
|
1120
1192
|
window.history.pushState({}, "", href);
|
|
1121
1193
|
setPath(href.split(/[?#]/)[0] ?? href);
|
|
1122
1194
|
}, []);
|
|
1123
|
-
const
|
|
1195
|
+
const active = chosen ?? pinned;
|
|
1196
|
+
useEffect4(() => {
|
|
1197
|
+
if (active !== undefined)
|
|
1198
|
+
document.documentElement.lang = active;
|
|
1199
|
+
}, [active]);
|
|
1200
|
+
const [headers, setHeaders] = useState4(0);
|
|
1201
|
+
const onHeaderMounted = useCallback3(() => {
|
|
1202
|
+
setHeaders((n) => n + 1);
|
|
1203
|
+
return () => setHeaders((n) => n - 1);
|
|
1204
|
+
}, []);
|
|
1205
|
+
const value = useMemo3(() => ({
|
|
1206
|
+
...context,
|
|
1207
|
+
navigate,
|
|
1208
|
+
onHeaderMounted,
|
|
1209
|
+
...chosen !== undefined ? { locale: chosen } : {},
|
|
1210
|
+
...pinned === undefined && offered && offered.length > 1 ? { setLocale } : {}
|
|
1211
|
+
}), [context, navigate, chosen, pinned, offered, setLocale, onHeaderMounted]);
|
|
1124
1212
|
const hit = resolveRoute(routes, path);
|
|
1125
1213
|
const isRoot = (path.replace(/\/+$/, "") || "/") === "/";
|
|
1126
1214
|
const matched = hit ? hit.route.render(hit.params) : fallback ?? (isRoot ? /* @__PURE__ */ jsx5(RouteIndex, {
|
|
@@ -1131,7 +1219,11 @@ function GroveRoutes(props) {
|
|
|
1131
1219
|
value,
|
|
1132
1220
|
children: [
|
|
1133
1221
|
children,
|
|
1134
|
-
screen
|
|
1222
|
+
screen,
|
|
1223
|
+
headers === 0 ? /* @__PURE__ */ jsx5("div", {
|
|
1224
|
+
className: "fixed right-3 bottom-3 z-50",
|
|
1225
|
+
children: /* @__PURE__ */ jsx5(LocaleSwitcher, {})
|
|
1226
|
+
}) : null
|
|
1135
1227
|
]
|
|
1136
1228
|
});
|
|
1137
1229
|
}
|
|
@@ -1379,6 +1471,7 @@ export {
|
|
|
1379
1471
|
GroveUiProvider,
|
|
1380
1472
|
Header,
|
|
1381
1473
|
Heading,
|
|
1474
|
+
LocaleSwitcher,
|
|
1382
1475
|
MarkdownField,
|
|
1383
1476
|
RelationSelect,
|
|
1384
1477
|
RouteIndex,
|
|
@@ -1387,6 +1480,7 @@ export {
|
|
|
1387
1480
|
Text,
|
|
1388
1481
|
detectLocale,
|
|
1389
1482
|
formatValue,
|
|
1483
|
+
localeLabel,
|
|
1390
1484
|
matchRoute,
|
|
1391
1485
|
renderMarkdown,
|
|
1392
1486
|
resolveHref,
|
package/dist/locale.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remembering the viewer's chosen language.
|
|
3
|
+
*
|
|
4
|
+
* Pure and storage-aware but React-free, so the rules below are testable without a renderer —
|
|
5
|
+
* the same split the dashboard uses between its `i18n.ts` and `i18n-context.tsx`.
|
|
6
|
+
*
|
|
7
|
+
* Two rules matter more than the storage itself:
|
|
8
|
+
* - A stored value is never trusted. It is user-editable, and it outlives the document that
|
|
9
|
+
* produced it: an app that dropped `de` must not keep serving German because a browser
|
|
10
|
+
* still remembers it. Anything not on offer falls back rather than sticking.
|
|
11
|
+
* - Storage is allowed to fail. It throws in a private window and when site data is blocked,
|
|
12
|
+
* and a language preference is never worth taking the page down for.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Where the choice lives. Namespaced under `grove.` like the generated app's own
|
|
16
|
+
* `grove.session`, and deliberately NOT the dashboard's `groveback.ui.locale`: this ships
|
|
17
|
+
* INTO a user's app, which may be served from the same origin as nothing in particular.
|
|
18
|
+
*/
|
|
19
|
+
export declare const LOCALE_STORAGE_KEY = "grove.locale";
|
|
20
|
+
/** The stored choice, if it is still one of `available`. */
|
|
21
|
+
export declare function readStoredLocale(available: readonly string[]): string | undefined;
|
|
22
|
+
/** Remember a choice. A failed write costs the memory, never the switch itself. */
|
|
23
|
+
export declare function storeLocale(locale: string): void;
|
|
24
|
+
/**
|
|
25
|
+
* How a language names ITSELF — "Español", not "Spanish". The person hunting for the switcher
|
|
26
|
+
* is the one who cannot read the language currently on screen, so every option is rendered in
|
|
27
|
+
* its own tongue.
|
|
28
|
+
*
|
|
29
|
+
* Document locales are arbitrary validated tags, so no shipped label table could cover them;
|
|
30
|
+
* `Intl.DisplayNames` knows them, and the raw tag is the honest fallback when it does not.
|
|
31
|
+
*/
|
|
32
|
+
export declare function localeLabel(locale: string): string;
|
package/dist/messages.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export interface UiMessages {
|
|
|
26
26
|
'common.no': string;
|
|
27
27
|
'header.openMenu': string;
|
|
28
28
|
'header.closeMenu': string;
|
|
29
|
+
/** Accessible name of the language switcher — the options label themselves. */
|
|
30
|
+
'locale.label': string;
|
|
29
31
|
'table.empty': string;
|
|
30
32
|
'form.save': string;
|
|
31
33
|
'form.saving': string;
|
package/dist/runtime.d.ts
CHANGED
|
@@ -21,6 +21,24 @@ export interface GroveUiContext {
|
|
|
21
21
|
/** Confirmation gate for destructive actions; defaults to window.confirm. */
|
|
22
22
|
confirm?: (message: string) => boolean | Promise<boolean>;
|
|
23
23
|
locale?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Every locale the document offers, default first — what a switcher puts on the menu.
|
|
26
|
+
* Absent, or shorter than two entries, means a monolingual app: no switcher renders.
|
|
27
|
+
*/
|
|
28
|
+
locales?: readonly string[] | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Switches the app's language. Supplied by `GroveRoutes`, which holds the locale in state
|
|
31
|
+
* and persists the choice; absent when an app drives `locale` itself as a fixed prop, and
|
|
32
|
+
* `LocaleSwitcher` then renders nothing rather than offering a control that does nothing.
|
|
33
|
+
*/
|
|
34
|
+
setLocale?: ((locale: string) => void) | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Called by `Header` while it is mounted — it returns its own cleanup, so the call site is
|
|
37
|
+
* a one-liner `useEffect`. The shell counts headers to decide whether to float a language
|
|
38
|
+
* switcher of its own: a document with no header, or a screen that opted out, renders none,
|
|
39
|
+
* and without this a multilingual app would ship a switcher nothing puts on the page.
|
|
40
|
+
*/
|
|
41
|
+
onHeaderMounted?: (() => () => void) | undefined;
|
|
24
42
|
currency?: string;
|
|
25
43
|
/**
|
|
26
44
|
* Overrides for the primitives' OWN strings (`messages.ts`) — the chrome an app never
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@groveback/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "React primitives for Groveback Studio screens — tables, forms, detail views and relation pickers that read through the Groveback SDK, so every query passes the policy engine.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|