@groveback/ui 0.1.1 → 0.3.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/README.md +38 -3
- package/dist/MarkdownField.d.ts +19 -0
- package/dist/SignIn.d.ts +38 -0
- package/dist/components.d.ts +27 -5
- package/dist/index.d.ts +9 -4
- package/dist/index.js +732 -99
- package/dist/routes.d.ts +85 -0
- package/dist/runtime.d.ts +20 -2
- package/dist/styles.d.ts +20 -0
- package/dist/types.d.ts +39 -7
- package/package.json +8 -6
- package/tailwind.css +12 -0
package/dist/routes.d.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The routing shell a generated app runs its screens in.
|
|
3
|
+
*
|
|
4
|
+
* `grove gen --ui` emits `routes.tsx` as DATA — the `ROUTES` table — and this module is the
|
|
5
|
+
* code that consumes it. The split is deliberate: the table is derived from the Studio
|
|
6
|
+
* document (routes, params, titles) and belongs to the user's repo, while the matcher and the
|
|
7
|
+
* History wiring are behaviour that should improve with `npm update`, not sit frozen in every
|
|
8
|
+
* app that was scaffolded before a fix.
|
|
9
|
+
*
|
|
10
|
+
* Nothing here depends on a router library. `ROUTES` is the useful part; an app that already
|
|
11
|
+
* uses React Router or TanStack feeds the table to that and never mounts `GroveRoutes`.
|
|
12
|
+
*/
|
|
13
|
+
import type { ReactNode } from 'react';
|
|
14
|
+
import type { GroveUiContext } from './runtime';
|
|
15
|
+
export interface GroveRoute {
|
|
16
|
+
/** Route pattern, `:param` segments included. */
|
|
17
|
+
path: string;
|
|
18
|
+
/** Title authored in the Studio, when the screen has one. */
|
|
19
|
+
title?: string | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Whether a visitor must be signed in to see this route (D81). Absent means REQUIRED, so a
|
|
22
|
+
* route table written before this existed keeps behaving as it did.
|
|
23
|
+
*/
|
|
24
|
+
requiresAuth?: boolean | undefined;
|
|
25
|
+
render: (params: Record<string, string>) => ReactNode;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Match a concrete path against a route pattern, capturing its params.
|
|
29
|
+
*
|
|
30
|
+
* Segment-wise, the same rule the Studio's validator and its preview use. Query and hash are
|
|
31
|
+
* ignored, a trailing slash is tolerated, and the root path is a real route: `/` must split to
|
|
32
|
+
* the same segments as the pattern `/`, which a bare "strip trailing slashes" would break by
|
|
33
|
+
* reducing it to an empty string.
|
|
34
|
+
*/
|
|
35
|
+
export declare function matchRoute(path: string, pattern: string): Record<string, string> | null;
|
|
36
|
+
/**
|
|
37
|
+
* The route a path opens, and the params it carries.
|
|
38
|
+
*
|
|
39
|
+
* Specific routes win over parameterised ones regardless of the order given: `/articles/new`
|
|
40
|
+
* is a real page, and `/articles/:id` would otherwise swallow it by reading "new" as an id.
|
|
41
|
+
* Generic over the route shape so the Studio preview can resolve against its screens with the
|
|
42
|
+
* same rule the generated app uses.
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveRoute<T extends {
|
|
45
|
+
path: string;
|
|
46
|
+
}>(routes: readonly T[], path: string): {
|
|
47
|
+
route: T;
|
|
48
|
+
params: Record<string, string>;
|
|
49
|
+
} | null;
|
|
50
|
+
/** The screen the current URL shows, under the provider the primitives dispatch through. */
|
|
51
|
+
export declare function GroveRoutes(props: {
|
|
52
|
+
routes: readonly GroveRoute[];
|
|
53
|
+
/**
|
|
54
|
+
* Everything the primitives need beyond navigation — `endpointBase`, `locale`, `currency`,
|
|
55
|
+
* `confirm`. Navigation itself is owned here, since it must update the matched route.
|
|
56
|
+
*/
|
|
57
|
+
context?: Omit<GroveUiContext, 'navigate'> | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* Rendered when no route matches. Default: at `/` with no home screen, an index of the
|
|
60
|
+
* document's pages (a generated app should never open on "Not found"); anywhere else, a
|
|
61
|
+
* plain "Not found" with a way home.
|
|
62
|
+
*/
|
|
63
|
+
fallback?: ReactNode | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* Rendered inside the provider, ahead of the screen — where an app puts its own chrome.
|
|
66
|
+
* A gate that covers EVERY route goes outside instead (`<SignIn><GroveRoutes …/></SignIn>`).
|
|
67
|
+
*/
|
|
68
|
+
children?: ReactNode | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* Rendered in place of a route whose `requiresAuth` is not false while nobody is signed in
|
|
71
|
+
* (D81) — the generated app passes its `<SignIn>`. Per route rather than around the whole
|
|
72
|
+
* router, so one app can serve a public landing page and a private dashboard.
|
|
73
|
+
*
|
|
74
|
+
* Absent means no gate at all: a route table with no public/private distinction renders as
|
|
75
|
+
* it always did, and an app that wraps the router keeps working unchanged.
|
|
76
|
+
*/
|
|
77
|
+
gate?: ((screen: ReactNode) => ReactNode) | undefined;
|
|
78
|
+
}): import("react").JSX.Element;
|
|
79
|
+
/**
|
|
80
|
+
* The pages of the document, for a root with no screen of its own. Only routes with no
|
|
81
|
+
* parameters are listed — `/books/:id` cannot be opened without a book.
|
|
82
|
+
*/
|
|
83
|
+
export declare function RouteIndex({ routes }: {
|
|
84
|
+
routes: readonly GroveRoute[];
|
|
85
|
+
}): import("react").JSX.Element;
|
package/dist/runtime.d.ts
CHANGED
|
@@ -9,7 +9,13 @@ import type { Action, CollectionLike, Doc, Format } from './types';
|
|
|
9
9
|
export interface GroveUiContext {
|
|
10
10
|
/** Navigate to a route. */
|
|
11
11
|
navigate: (href: string) => void;
|
|
12
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Calls one of the project's custom endpoints WITH the app's session — `grove.client.run`
|
|
14
|
+
* in a generated app. Preferred over `endpointBase`, which is a bare `fetch` that carries
|
|
15
|
+
* no token and so runs the endpoint as nobody.
|
|
16
|
+
*/
|
|
17
|
+
callEndpoint?: (path: string, method: 'POST' | 'GET') => Promise<unknown>;
|
|
18
|
+
/** Base URL for custom endpoint (`/run/*`) actions when no `callEndpoint` is given. */
|
|
13
19
|
endpointBase?: string;
|
|
14
20
|
/** Confirmation gate for destructive actions; defaults to window.confirm. */
|
|
15
21
|
confirm?: (message: string) => boolean | Promise<boolean>;
|
|
@@ -43,8 +49,20 @@ export interface DispatchOptions {
|
|
|
43
49
|
* left to whoever authored the screen.
|
|
44
50
|
*/
|
|
45
51
|
export declare function useAction(): (action: Action, opts?: DispatchOptions) => Promise<void>;
|
|
46
|
-
/** Render a stored value for display. Never throws — a bad value shows as empty. */
|
|
47
52
|
export declare function formatValue(value: unknown, format: Format | undefined, opts?: {
|
|
48
53
|
locale?: string | undefined;
|
|
49
54
|
currency?: string | undefined;
|
|
50
55
|
}): string;
|
|
56
|
+
/**
|
|
57
|
+
* A translation lookup over the catalogue a multilingual screen carries, reading the active
|
|
58
|
+
* locale from the provider. The screen keeps its MESSAGES; the locale lives in ONE place —
|
|
59
|
+
* the routing shell's context — instead of a module variable per screen that nothing could
|
|
60
|
+
* set for all of them at once.
|
|
61
|
+
*/
|
|
62
|
+
export declare function useMessages(messages: Record<string, Record<string, string>>, fallback: string): (key: string) => string;
|
|
63
|
+
/**
|
|
64
|
+
* The locale to start in: the first of the viewer's preferred languages the document has,
|
|
65
|
+
* matched exactly and then by language (`es-MX` finds `es`), else the document's first —
|
|
66
|
+
* its default — locale.
|
|
67
|
+
*/
|
|
68
|
+
export declare function detectLocale(locales: readonly string[], preferred?: readonly string[]): string;
|
package/dist/styles.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The class strings the primitives share, in one place so no two components can drift apart
|
|
3
|
+
* on the same control. Internal: nothing here is exported from the package.
|
|
4
|
+
*
|
|
5
|
+
* Both halves of every pair are declared. A control that paints a background must also paint
|
|
6
|
+
* its foreground, and it must state the LIGHT case rather than leaving it to inherit: a bare
|
|
7
|
+
* `dark:bg-neutral-950` fires on a viewer whose OS is in dark mode even when the surrounding
|
|
8
|
+
* page is light, which left a black input holding black text — invisible — on a white page.
|
|
9
|
+
*/
|
|
10
|
+
import type { ButtonVariant } from './types';
|
|
11
|
+
export declare function cx(...parts: Array<string | false | undefined>): string;
|
|
12
|
+
/** The one input skin, shared by text/number/date inputs and the enum select. */
|
|
13
|
+
export declare const INPUT_CLASS: string;
|
|
14
|
+
export declare const BUTTON_BASE: string;
|
|
15
|
+
export declare const VARIANT: Record<ButtonVariant, string>;
|
|
16
|
+
export declare const TONE: {
|
|
17
|
+
readonly error: "text-sm text-red-600";
|
|
18
|
+
readonly success: "text-sm text-green-700 dark:text-green-500";
|
|
19
|
+
readonly muted: "text-sm text-neutral-500";
|
|
20
|
+
};
|
package/dist/types.d.ts
CHANGED
|
@@ -5,22 +5,54 @@
|
|
|
5
5
|
*/
|
|
6
6
|
/** Minimal shape of a document as the SDK returns it. */
|
|
7
7
|
export type Doc = Record<string, unknown>;
|
|
8
|
-
/**
|
|
9
|
-
|
|
8
|
+
/**
|
|
9
|
+
* The slice of the SDK's collection surface the primitives use.
|
|
10
|
+
*
|
|
11
|
+
* Generic in the document type so a `grove gen` TYPED collection is assignable. It cannot be
|
|
12
|
+
* `Doc` alone: a generated `interface Customers` has no index signature, and TypeScript does
|
|
13
|
+
* not give interfaces one implicitly, so `TypedCollection<Customers, …>` failed to satisfy
|
|
14
|
+
* `Promise<Doc[]>` — every screen `grove gen --ui` writes was a type error in the user's app.
|
|
15
|
+
*
|
|
16
|
+
* `TInput` is separate for the same reason on the write side: a generated `CustomersInput`
|
|
17
|
+
* has REQUIRED fields, so a `create(doc: Doc)` signature is not one it can satisfy. Both
|
|
18
|
+
* parameters default to `Doc`, which is what an untyped collection and the preview pass.
|
|
19
|
+
*/
|
|
20
|
+
export interface CollectionLike<TDoc extends Doc = Doc, TInput = Doc> {
|
|
10
21
|
find(opts?: {
|
|
11
22
|
filter?: Record<string, unknown>;
|
|
12
23
|
limit?: number;
|
|
13
24
|
skip?: number;
|
|
14
|
-
}): Promise<
|
|
15
|
-
get(id: string): Promise<
|
|
16
|
-
create(doc:
|
|
17
|
-
update(id: string, patch: Doc): Promise<void>;
|
|
25
|
+
}): Promise<TDoc[]>;
|
|
26
|
+
get(id: string): Promise<TDoc>;
|
|
27
|
+
create(doc: TInput): Promise<TDoc>;
|
|
28
|
+
update(id: string, patch: Partial<TInput> & Doc): Promise<void>;
|
|
18
29
|
delete(id: string): Promise<void>;
|
|
19
30
|
}
|
|
20
|
-
|
|
31
|
+
/**
|
|
32
|
+
* A collection of ANY document/input shape — what the primitives' props accept.
|
|
33
|
+
*
|
|
34
|
+
* The props cannot say `CollectionLike` (that is `CollectionLike<Doc, Doc>`, which a typed
|
|
35
|
+
* `TypedCollection<Customers, CustomersInput>` does not satisfy: `create` would have to take
|
|
36
|
+
* a loose `Doc` where the generated input has required fields). Making every component
|
|
37
|
+
* generic would push those parameters into four public prop types for no gain, since a
|
|
38
|
+
* primitive never uses the row type — it reads fields by a name that came from the IR.
|
|
39
|
+
*
|
|
40
|
+
* So the props are deliberately shape-agnostic here, and the type safety lives where it is
|
|
41
|
+
* useful: in the caller's own code, where `grove.collections.customers` is still fully typed.
|
|
42
|
+
*
|
|
43
|
+
* `any` for the input is what makes BOTH directions work — a Form assembles a plain object
|
|
44
|
+
* and passes it to `create`, while a generated collection demands its own required fields.
|
|
45
|
+
* It is confined to this one alias and never reaches a caller's types.
|
|
46
|
+
*/
|
|
47
|
+
export type AnyCollection = CollectionLike<Doc, any>;
|
|
48
|
+
export type Format = 'text' | 'markdown' | 'number' | 'currency' | 'date' | 'boolean';
|
|
21
49
|
export interface FieldRef {
|
|
22
50
|
field: string;
|
|
23
51
|
label: string;
|
|
52
|
+
/** Allowed values (the schema's `enum`). A form renders these as a select. */
|
|
53
|
+
options?: string[] | undefined;
|
|
54
|
+
/** Column width in a table: `narrow` | `normal` | `wide`. Ignored outside a DataTable. */
|
|
55
|
+
width?: 'narrow' | 'normal' | 'wide' | undefined;
|
|
24
56
|
format?: Format;
|
|
25
57
|
/** For an x-ref field: show this field of the referenced document instead of the id. */
|
|
26
58
|
display?: {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@groveback/ui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "React primitives for Groveback Studio screens
|
|
3
|
+
"version": "0.3.0",
|
|
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",
|
|
7
7
|
"module": "./dist/index.js",
|
|
@@ -10,15 +10,17 @@
|
|
|
10
10
|
".": {
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"import": "./dist/index.js"
|
|
13
|
-
}
|
|
13
|
+
},
|
|
14
|
+
"./tailwind.css": "./tailwind.css"
|
|
14
15
|
},
|
|
15
16
|
"files": [
|
|
16
|
-
"dist"
|
|
17
|
+
"dist",
|
|
18
|
+
"tailwind.css"
|
|
17
19
|
],
|
|
18
|
-
"//sideEffects": "NOT set to false: bun 1.4's bundler treats it as licence to drop the entire entry point, emitting the export names with nothing behind them
|
|
20
|
+
"//sideEffects": "NOT set to false: bun 1.4's bundler treats it as licence to drop the entire entry point, emitting the export names with nothing behind them — a 193-byte module exporting symbols that do not exist, reported as a successful build. The check-bundle script guards against a regression.",
|
|
19
21
|
"scripts": {
|
|
20
22
|
"build": "rm -rf dist && bun build ./src/index.ts --outdir dist --target browser --format esm --external react --external react-dom && bun run check-bundle && bunx tsc -p tsconfig.json && cp ../../LICENSE .",
|
|
21
|
-
"check-bundle": "bun -e \"const s=(await Bun.file('dist/index.js').text()); if(!s.includes('DataTable')||s.length<5000){console.error('dist/index.js looks empty ('+s.length+' bytes)
|
|
23
|
+
"check-bundle": "bun -e \"const s=(await Bun.file('dist/index.js').text()); if(!s.includes('DataTable')||s.length<5000){console.error('dist/index.js looks empty ('+s.length+' bytes) — the bundler dropped modules');process.exit(1)}\""
|
|
22
24
|
},
|
|
23
25
|
"peerDependencies": {
|
|
24
26
|
"react": ">=18",
|
package/tailwind.css
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Tell Tailwind where @groveback/ui's classes live.
|
|
3
|
+
*
|
|
4
|
+
* The primitives are styled with Tailwind utilities but ship no CSS of their own, so the
|
|
5
|
+
* app's Tailwind build has to scan this package or every class goes unemitted and the
|
|
6
|
+
* screens render unstyled. `@source` here is relative to THIS file, so importing it works
|
|
7
|
+
* from any install layout (hoisted, pnpm, a workspace link) without the app guessing a
|
|
8
|
+
* node_modules path. In your app's CSS, after `@import "tailwindcss";`:
|
|
9
|
+
*
|
|
10
|
+
* @import "@groveback/ui/tailwind.css";
|
|
11
|
+
*/
|
|
12
|
+
@source "./dist/index.js";
|