@minlang/design-system 0.1.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/LICENSE +21 -0
- package/dist/a11y/FieldError.d.ts +4 -0
- package/dist/a11y/FieldError.js +6 -0
- package/dist/a11y/Label.d.ts +5 -0
- package/dist/a11y/Label.js +2 -0
- package/dist/a11y/LiveRegion.d.ts +4 -0
- package/dist/a11y/LiveRegion.js +3 -0
- package/dist/a11y/SkipLink.d.ts +4 -0
- package/dist/a11y/SkipLink.js +3 -0
- package/dist/a11y/VisuallyHidden.d.ts +4 -0
- package/dist/a11y/VisuallyHidden.js +2 -0
- package/dist/a11y/useFocusAlert.d.ts +1 -0
- package/dist/a11y/useFocusAlert.js +13 -0
- package/dist/components/ActionButton.d.ts +8 -0
- package/dist/components/ActionButton.js +17 -0
- package/dist/components/ActionButtonClient.d.ts +9 -0
- package/dist/components/ActionButtonClient.js +37 -0
- package/dist/components/ActionForm.d.ts +3 -0
- package/dist/components/ActionForm.js +7 -0
- package/dist/components/ActionFormClient.d.ts +14 -0
- package/dist/components/ActionFormClient.js +32 -0
- package/dist/components/CardGrid.d.ts +3 -0
- package/dist/components/CardGrid.js +9 -0
- package/dist/components/DataTable.d.ts +3 -0
- package/dist/components/DataTable.js +9 -0
- package/dist/components/EmptyState.d.ts +3 -0
- package/dist/components/EmptyState.js +2 -0
- package/dist/components/FormField.d.ts +11 -0
- package/dist/components/FormField.js +17 -0
- package/dist/components/Heading.d.ts +3 -0
- package/dist/components/Heading.js +3 -0
- package/dist/components/HeadlineBanner.d.ts +3 -0
- package/dist/components/HeadlineBanner.js +10 -0
- package/dist/components/HintText.d.ts +3 -0
- package/dist/components/HintText.js +2 -0
- package/dist/components/PrimaryActionButton.d.ts +3 -0
- package/dist/components/PrimaryActionButton.js +4 -0
- package/dist/components/SecondaryActionButton.d.ts +3 -0
- package/dist/components/SecondaryActionButton.js +4 -0
- package/dist/components/StatusBadge.d.ts +3 -0
- package/dist/components/StatusBadge.js +10 -0
- package/dist/components/TapTargetButton.d.ts +3 -0
- package/dist/components/TapTargetButton.js +4 -0
- package/dist/components/TextBlock.d.ts +3 -0
- package/dist/components/TextBlock.js +2 -0
- package/dist/components/UnknownWidget.d.ts +3 -0
- package/dist/components/UnknownWidget.js +2 -0
- package/dist/components/cellText.d.ts +3 -0
- package/dist/components/cellText.js +14 -0
- package/dist/components/fieldOptions.d.ts +3 -0
- package/dist/components/fieldOptions.js +15 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +26 -0
- package/dist/registry.d.ts +2 -0
- package/dist/registry.js +32 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Codeshift AI Solutions
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// Error text linked to a control via aria-describedby (the id contract).
|
|
3
|
+
// Focusable (tabIndex -1) so forms can move focus to the rejection.
|
|
4
|
+
import { forwardRef } from "react";
|
|
5
|
+
export const FieldError = forwardRef(({ id, message }, ref) => message === null ? null : (_jsx("p", { ref: ref, id: id, role: "alert", tabIndex: -1, className: "mt-ml-xs text-sm text-danger", children: message })));
|
|
6
|
+
FieldError.displayName = "FieldError";
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// Keyboard skip-to-content link; visible on focus.
|
|
3
|
+
export const SkipLink = ({ targetId }) => (_jsx("a", { href: `#${targetId}`, className: "sr-only focus:not-sr-only focus:absolute focus:left-ml-sm focus:top-ml-sm focus:rounded-ml-sm focus:bg-surface-raised focus:p-ml-sm focus:text-ink", children: "Skip to content" }));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const useFocusAlert: (message: string | null) => React.RefObject<HTMLParagraphElement | null>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
// Moves focus to the (first) error alert when a rejection lands, so keyboard
|
|
3
|
+
// and screen-reader users hear the constraint message immediately.
|
|
4
|
+
import { useEffect, useRef } from "react";
|
|
5
|
+
export const useFocusAlert = (message) => {
|
|
6
|
+
const ref = useRef(null);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (message !== null) {
|
|
9
|
+
ref.current?.focus();
|
|
10
|
+
}
|
|
11
|
+
}, [message]);
|
|
12
|
+
return ref;
|
|
13
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DispatchBinding, RenderDeps } from "@minlang/runtime-web";
|
|
2
|
+
import type { ReactElement } from "react";
|
|
3
|
+
export declare const ActionButton: ({ label, dispatch, deps, variant, }: Readonly<{
|
|
4
|
+
label: string;
|
|
5
|
+
dispatch: DispatchBinding;
|
|
6
|
+
deps: RenderDeps;
|
|
7
|
+
variant: "primary" | "secondary";
|
|
8
|
+
}>) => ReactElement;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { ActionButtonClient } from "./ActionButtonClient.js";
|
|
3
|
+
export const ActionButton = ({ label, dispatch, deps, variant, }) => {
|
|
4
|
+
const names = dispatch.kind === "action" ? [dispatch.action] : dispatch.actions;
|
|
5
|
+
const handlers = [];
|
|
6
|
+
let missing = null;
|
|
7
|
+
for (const name of names) {
|
|
8
|
+
const handler = deps.actions[name];
|
|
9
|
+
if (handler === undefined) {
|
|
10
|
+
missing = missing ?? name;
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
handlers.push(handler);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return (_jsx(ActionButtonClient, { label: label, variant: variant, handlers: handlers, missingHandler: missing }));
|
|
17
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ServerActionHandler } from "@minlang/runtime-web";
|
|
2
|
+
import type { ReactElement } from "react";
|
|
3
|
+
export type ActionButtonClientProps = Readonly<{
|
|
4
|
+
label: string;
|
|
5
|
+
variant: "primary" | "secondary";
|
|
6
|
+
handlers: ReadonlyArray<ServerActionHandler>;
|
|
7
|
+
missingHandler: string | null;
|
|
8
|
+
}>;
|
|
9
|
+
export declare const ActionButtonClient: (props: ActionButtonClientProps) => ReactElement;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// Client action button: runs its server-action handlers in order, stopping
|
|
4
|
+
// at the first rejection; announces errors and moves focus to them.
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { FieldError } from "../a11y/FieldError.js";
|
|
7
|
+
import { LiveRegion } from "../a11y/LiveRegion.js";
|
|
8
|
+
import { useFocusAlert } from "../a11y/useFocusAlert.js";
|
|
9
|
+
const runHandlers = async (handlers) => {
|
|
10
|
+
for (const handler of handlers) {
|
|
11
|
+
const outcome = await handler(new FormData());
|
|
12
|
+
if (!outcome.ok) {
|
|
13
|
+
return outcome.error.message;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
};
|
|
18
|
+
export const ActionButtonClient = (props) => {
|
|
19
|
+
const [error, setError] = useState(null);
|
|
20
|
+
const [pending, setPending] = useState(false);
|
|
21
|
+
const alertRef = useFocusAlert(error);
|
|
22
|
+
const styles = props.variant === "primary"
|
|
23
|
+
? "bg-primary text-primary-ink hover:opacity-90"
|
|
24
|
+
: "border border-edge bg-surface-raised text-ink hover:bg-surface";
|
|
25
|
+
const onClick = () => {
|
|
26
|
+
if (props.missingHandler !== null) {
|
|
27
|
+
setError(`No handler bound for action ${props.missingHandler}.`);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
setPending(true);
|
|
31
|
+
void runHandlers(props.handlers).then((outcome) => {
|
|
32
|
+
setPending(false);
|
|
33
|
+
setError(outcome);
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
return (_jsxs("span", { className: "inline-flex flex-col gap-ml-xs", children: [_jsx("button", { type: "button", disabled: pending, onClick: onClick, className: `rounded-ml-md px-ml-md py-ml-sm font-medium transition-colors duration-ml-fast focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring disabled:opacity-60 ${styles}`, children: props.label }), _jsx(FieldError, { ref: alertRef, id: `action-error-${props.label}`, message: error }), _jsx(LiveRegion, { message: error })] }));
|
|
37
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { ActionFormClient } from "./ActionFormClient.js";
|
|
3
|
+
import { fieldOptions } from "./fieldOptions.js";
|
|
4
|
+
export const ActionForm = ({ node, deps }) => (_jsx(ActionFormClient, { label: node.a11y.label, submitLabel: node.submitLabel, errorId: `form-error-${node.action}`, fields: node.fields.map((field) => ({
|
|
5
|
+
field,
|
|
6
|
+
options: fieldOptions(field, deps.state),
|
|
7
|
+
})), handler: deps.actions[node.action] ?? null }));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import type { FieldSpec, ServerActionHandler } from "@minlang/runtime-web";
|
|
3
|
+
import type { SelectOption } from "./FormField.js";
|
|
4
|
+
export type ActionFormClientProps = Readonly<{
|
|
5
|
+
label: string;
|
|
6
|
+
submitLabel: string;
|
|
7
|
+
errorId: string;
|
|
8
|
+
fields: ReadonlyArray<Readonly<{
|
|
9
|
+
field: FieldSpec;
|
|
10
|
+
options: ReadonlyArray<SelectOption>;
|
|
11
|
+
}>>;
|
|
12
|
+
handler: ServerActionHandler | null;
|
|
13
|
+
}>;
|
|
14
|
+
export declare const ActionFormClient: (props: ActionFormClientProps) => ReactElement;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// Client half of the schema form: submits FormData to the bound server
|
|
4
|
+
// action, announces rejections (exact constraint message), moves focus.
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { FieldError } from "../a11y/FieldError.js";
|
|
7
|
+
import { LiveRegion } from "../a11y/LiveRegion.js";
|
|
8
|
+
import { useFocusAlert } from "../a11y/useFocusAlert.js";
|
|
9
|
+
import { FormField } from "./FormField.js";
|
|
10
|
+
export const ActionFormClient = (props) => {
|
|
11
|
+
const [error, setError] = useState(null);
|
|
12
|
+
const [pending, setPending] = useState(false);
|
|
13
|
+
const alertRef = useFocusAlert(error);
|
|
14
|
+
const onSubmit = (event) => {
|
|
15
|
+
event.preventDefault();
|
|
16
|
+
const form = event.currentTarget;
|
|
17
|
+
if (props.handler === null) {
|
|
18
|
+
setError("This action is not available.");
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const handler = props.handler;
|
|
22
|
+
setPending(true);
|
|
23
|
+
void handler(new FormData(form)).then((outcome) => {
|
|
24
|
+
setPending(false);
|
|
25
|
+
setError(outcome.ok ? null : outcome.error.message);
|
|
26
|
+
if (outcome.ok) {
|
|
27
|
+
form.reset();
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
return (_jsxs("form", { onSubmit: onSubmit, "aria-label": props.label, "aria-describedby": error === null ? undefined : props.errorId, className: "flex w-full max-w-md flex-col gap-ml-sm rounded-ml-md border border-edge bg-surface-raised p-ml-md", children: [props.fields.map(({ field, options }) => (_jsx(FormField, { idPrefix: props.label, field: field, options: options }, field.name))), _jsx(FieldError, { ref: alertRef, id: props.errorId, message: error }), _jsx("button", { type: "submit", disabled: pending, className: "rounded-ml-md bg-primary px-ml-md py-ml-sm font-medium text-primary-ink transition-colors duration-ml-fast hover:opacity-90 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring disabled:opacity-60", children: props.submitLabel }), _jsx(LiveRegion, { message: error })] }));
|
|
32
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cellText, rowsFromViewModel } from "./cellText.js";
|
|
3
|
+
export const CardGrid = ({ node, deps }) => {
|
|
4
|
+
const rows = rowsFromViewModel(deps.viewModels[node.query]);
|
|
5
|
+
if (rows.length === 0) {
|
|
6
|
+
return _jsx("p", { role: "note", className: "text-ink-muted", children: node.emptyState.text });
|
|
7
|
+
}
|
|
8
|
+
return (_jsx("ul", { "aria-label": node.a11y.label, className: "grid gap-ml-sm", style: { gridTemplateColumns: `repeat(${node.columnsPerRow}, minmax(0, 1fr))` }, children: rows.map((row) => (_jsx("li", { className: "rounded-ml-md border border-edge bg-surface-raised p-ml-sm text-sm text-ink", children: node.columns.map((column) => (_jsxs("p", { children: [_jsxs("span", { className: "text-ink-muted", children: [column.label, ": "] }), cellText(row, column)] }, column.field))) }, String(row[node.rowKey])))) }));
|
|
9
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cellText, rowsFromViewModel } from "./cellText.js";
|
|
3
|
+
export const DataTable = ({ node, deps }) => {
|
|
4
|
+
const rows = rowsFromViewModel(deps.viewModels[node.query]);
|
|
5
|
+
if (rows.length === 0) {
|
|
6
|
+
return (_jsx("p", { role: "note", className: "rounded-ml-md border border-dashed border-edge p-ml-lg text-center text-ink-muted", children: node.emptyState.text }));
|
|
7
|
+
}
|
|
8
|
+
return (_jsx("div", { className: "overflow-x-auto rounded-ml-md border border-edge bg-surface-raised", children: _jsxs("table", { "aria-label": node.a11y.label, className: "w-full border-collapse text-left text-sm", children: [_jsx("thead", { children: _jsx("tr", { className: "border-b border-edge", children: node.columns.map((column) => (_jsx("th", { scope: "col", className: "px-ml-md py-ml-sm font-semibold text-ink", children: column.label }, column.field))) }) }), _jsx("tbody", { children: rows.map((row) => (_jsx("tr", { className: "border-b border-edge last:border-b-0", children: node.columns.map((column) => (_jsx("td", { className: "px-ml-md py-ml-sm text-ink", children: cellText(row, column) }, column.field))) }, String(row[node.rowKey])))) })] }) }));
|
|
9
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FieldSpec } from "@minlang/runtime-web";
|
|
2
|
+
import type { ReactElement } from "react";
|
|
3
|
+
export type SelectOption = Readonly<{
|
|
4
|
+
value: string;
|
|
5
|
+
label: string;
|
|
6
|
+
}>;
|
|
7
|
+
export declare const FormField: ({ idPrefix, field, options, }: Readonly<{
|
|
8
|
+
idPrefix: string;
|
|
9
|
+
field: FieldSpec;
|
|
10
|
+
options: ReadonlyArray<SelectOption>;
|
|
11
|
+
}>) => ReactElement;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Label } from "../a11y/Label.js";
|
|
3
|
+
const inputClass = "w-full rounded-ml-sm border border-edge bg-surface-raised px-ml-sm py-ml-xs text-ink focus-visible:outline focus-visible:outline-2 focus-visible:outline-focus-ring";
|
|
4
|
+
const Control = ({ id, field, options, }) => {
|
|
5
|
+
if (field.control === "select") {
|
|
6
|
+
return (_jsx("select", { id: id, name: field.name, className: inputClass, children: options.map((option) => (_jsx("option", { value: option.value, children: option.label }, option.value))) }));
|
|
7
|
+
}
|
|
8
|
+
if (field.control === "checkbox") {
|
|
9
|
+
return _jsx("input", { id: id, name: field.name, type: "checkbox", className: "h-4 w-4 accent-primary" });
|
|
10
|
+
}
|
|
11
|
+
const type = field.control === "number" ? "number" : field.control === "datetime" ? "datetime-local" : "text";
|
|
12
|
+
return _jsx("input", { id: id, name: field.name, type: type, className: inputClass });
|
|
13
|
+
};
|
|
14
|
+
export const FormField = ({ idPrefix, field, options, }) => {
|
|
15
|
+
const id = `field-${idPrefix}-${field.name}`;
|
|
16
|
+
return (_jsxs("div", { className: "flex flex-col gap-ml-xs", children: [_jsx(Label, { htmlFor: id, children: field.label }), _jsx(Control, { id: id, field: field, options: options })] }));
|
|
17
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// Outcome headline (e.g. round results); copy from the schema binding.
|
|
3
|
+
import { resolveCopy } from "@minlang/runtime-web";
|
|
4
|
+
export const HeadlineBanner = ({ node, deps }) => {
|
|
5
|
+
const copy = resolveCopy(node.copy, deps.state);
|
|
6
|
+
if (copy === "") {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
return (_jsx("p", { role: "status", className: "rounded-ml-md bg-surface-raised px-ml-md py-ml-sm text-xl font-bold text-ink", children: copy }));
|
|
10
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { ActionButton } from "./ActionButton.js";
|
|
4
|
+
export const PrimaryActionButton = ({ node, deps }) => (_jsx(ActionButton, { label: node.label, dispatch: node.dispatch, deps: deps, variant: "primary" }));
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { ActionButton } from "./ActionButton.js";
|
|
4
|
+
export const SecondaryActionButton = ({ node, deps, }) => (_jsx(ActionButton, { label: node.label, dispatch: node.dispatch, deps: deps, variant: "secondary" }));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// Enum-driven status badge; copy resolved from the screen schema binding.
|
|
3
|
+
import { resolveCopy } from "@minlang/runtime-web";
|
|
4
|
+
export const StatusBadge = ({ node, deps }) => {
|
|
5
|
+
const copy = resolveCopy(node.copy, deps.state);
|
|
6
|
+
if (copy === "") {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
return (_jsx("span", { role: "status", className: "inline-block w-fit rounded-ml-pill border border-edge bg-surface-raised px-ml-sm py-ml-xs text-sm font-medium text-ink", children: copy }));
|
|
10
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { ActionButton } from "./ActionButton.js";
|
|
4
|
+
export const TapTargetButton = ({ node, deps }) => (_jsx(ActionButton, { label: `Tap ${node.target}`, dispatch: node.dispatch, deps: deps, variant: "secondary" }));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Cell formatting for collection widgets: plain, deterministic text.
|
|
2
|
+
export const cellText = (row, column) => {
|
|
3
|
+
const value = row[column.field];
|
|
4
|
+
if (typeof value === "boolean") {
|
|
5
|
+
return value ? "Yes" : "No";
|
|
6
|
+
}
|
|
7
|
+
if (value === undefined || value === null) {
|
|
8
|
+
return "";
|
|
9
|
+
}
|
|
10
|
+
return String(value);
|
|
11
|
+
};
|
|
12
|
+
export const rowsFromViewModel = (value) => Array.isArray(value)
|
|
13
|
+
? value.filter((row) => typeof row === "object" && row !== null)
|
|
14
|
+
: [];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Server-side option resolution for select fields: ref fields offer state
|
|
2
|
+
// rows (id + human label); enum fields offer their members. Serializable.
|
|
3
|
+
import { rowLabel, rowsOf } from "@minlang/runtime-web";
|
|
4
|
+
export const fieldOptions = (field, state) => {
|
|
5
|
+
if (field.control !== "select") {
|
|
6
|
+
return [];
|
|
7
|
+
}
|
|
8
|
+
if (field.refEntity !== null) {
|
|
9
|
+
return rowsOf(state, field.refEntity).map((row) => ({
|
|
10
|
+
value: String(row["id"] ?? ""),
|
|
11
|
+
label: rowLabel(row),
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
14
|
+
return (field.enumMembers ?? []).map((member) => ({ value: member, label: member }));
|
|
15
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export { FieldError } from "./a11y/FieldError.js";
|
|
2
|
+
export { Label } from "./a11y/Label.js";
|
|
3
|
+
export { LiveRegion } from "./a11y/LiveRegion.js";
|
|
4
|
+
export { SkipLink } from "./a11y/SkipLink.js";
|
|
5
|
+
export { useFocusAlert } from "./a11y/useFocusAlert.js";
|
|
6
|
+
export { VisuallyHidden } from "./a11y/VisuallyHidden.js";
|
|
7
|
+
export { ActionButton } from "./components/ActionButton.js";
|
|
8
|
+
export { ActionButtonClient } from "./components/ActionButtonClient.js";
|
|
9
|
+
export { ActionForm } from "./components/ActionForm.js";
|
|
10
|
+
export { ActionFormClient } from "./components/ActionFormClient.js";
|
|
11
|
+
export { CardGrid } from "./components/CardGrid.js";
|
|
12
|
+
export { DataTable } from "./components/DataTable.js";
|
|
13
|
+
export { EmptyState } from "./components/EmptyState.js";
|
|
14
|
+
export { FormField } from "./components/FormField.js";
|
|
15
|
+
export { Heading } from "./components/Heading.js";
|
|
16
|
+
export { HeadlineBanner } from "./components/HeadlineBanner.js";
|
|
17
|
+
export { HintText } from "./components/HintText.js";
|
|
18
|
+
export { PrimaryActionButton } from "./components/PrimaryActionButton.js";
|
|
19
|
+
export { SecondaryActionButton } from "./components/SecondaryActionButton.js";
|
|
20
|
+
export { StatusBadge } from "./components/StatusBadge.js";
|
|
21
|
+
export { TapTargetButton } from "./components/TapTargetButton.js";
|
|
22
|
+
export { TextBlock } from "./components/TextBlock.js";
|
|
23
|
+
export { UnknownWidget } from "./components/UnknownWidget.js";
|
|
24
|
+
export { defaultRegistry } from "./registry.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// @minlang/design-system public surface: the default registry, components,
|
|
2
|
+
// and a11y primitives. Named exports only; no domain logic.
|
|
3
|
+
export { FieldError } from "./a11y/FieldError.js";
|
|
4
|
+
export { Label } from "./a11y/Label.js";
|
|
5
|
+
export { LiveRegion } from "./a11y/LiveRegion.js";
|
|
6
|
+
export { SkipLink } from "./a11y/SkipLink.js";
|
|
7
|
+
export { useFocusAlert } from "./a11y/useFocusAlert.js";
|
|
8
|
+
export { VisuallyHidden } from "./a11y/VisuallyHidden.js";
|
|
9
|
+
export { ActionButton } from "./components/ActionButton.js";
|
|
10
|
+
export { ActionButtonClient } from "./components/ActionButtonClient.js";
|
|
11
|
+
export { ActionForm } from "./components/ActionForm.js";
|
|
12
|
+
export { ActionFormClient } from "./components/ActionFormClient.js";
|
|
13
|
+
export { CardGrid } from "./components/CardGrid.js";
|
|
14
|
+
export { DataTable } from "./components/DataTable.js";
|
|
15
|
+
export { EmptyState } from "./components/EmptyState.js";
|
|
16
|
+
export { FormField } from "./components/FormField.js";
|
|
17
|
+
export { Heading } from "./components/Heading.js";
|
|
18
|
+
export { HeadlineBanner } from "./components/HeadlineBanner.js";
|
|
19
|
+
export { HintText } from "./components/HintText.js";
|
|
20
|
+
export { PrimaryActionButton } from "./components/PrimaryActionButton.js";
|
|
21
|
+
export { SecondaryActionButton } from "./components/SecondaryActionButton.js";
|
|
22
|
+
export { StatusBadge } from "./components/StatusBadge.js";
|
|
23
|
+
export { TapTargetButton } from "./components/TapTargetButton.js";
|
|
24
|
+
export { TextBlock } from "./components/TextBlock.js";
|
|
25
|
+
export { UnknownWidget } from "./components/UnknownWidget.js";
|
|
26
|
+
export { defaultRegistry } from "./registry.js";
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// The default component registry: every widget key resolves to a curated,
|
|
2
|
+
// token-styled, accessible component. Compile-time exhaustive — adding a
|
|
3
|
+
// widget key in the runtime contract without a component here is a type
|
|
4
|
+
// error. No domain logic in any component.
|
|
5
|
+
import { ActionForm } from "./components/ActionForm.js";
|
|
6
|
+
import { CardGrid } from "./components/CardGrid.js";
|
|
7
|
+
import { DataTable } from "./components/DataTable.js";
|
|
8
|
+
import { EmptyState } from "./components/EmptyState.js";
|
|
9
|
+
import { Heading } from "./components/Heading.js";
|
|
10
|
+
import { HeadlineBanner } from "./components/HeadlineBanner.js";
|
|
11
|
+
import { HintText } from "./components/HintText.js";
|
|
12
|
+
import { PrimaryActionButton } from "./components/PrimaryActionButton.js";
|
|
13
|
+
import { SecondaryActionButton } from "./components/SecondaryActionButton.js";
|
|
14
|
+
import { StatusBadge } from "./components/StatusBadge.js";
|
|
15
|
+
import { TapTargetButton } from "./components/TapTargetButton.js";
|
|
16
|
+
import { TextBlock } from "./components/TextBlock.js";
|
|
17
|
+
import { UnknownWidget } from "./components/UnknownWidget.js";
|
|
18
|
+
export const defaultRegistry = {
|
|
19
|
+
heading: Heading,
|
|
20
|
+
text: TextBlock,
|
|
21
|
+
hint: HintText,
|
|
22
|
+
status_badge: StatusBadge,
|
|
23
|
+
headline: HeadlineBanner,
|
|
24
|
+
table: DataTable,
|
|
25
|
+
card_grid: CardGrid,
|
|
26
|
+
form: ActionForm,
|
|
27
|
+
primary_action: PrimaryActionButton,
|
|
28
|
+
secondary_action: SecondaryActionButton,
|
|
29
|
+
tap_target: TapTargetButton,
|
|
30
|
+
empty_state: EmptyState,
|
|
31
|
+
unknown: UnknownWidget,
|
|
32
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@minlang/design-system",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"default": "./dist/index.js"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@minlang/runtime-web": "^0.1.0"
|
|
13
|
+
},
|
|
14
|
+
"peerDependencies": {
|
|
15
|
+
"react": "19.2.7"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@testing-library/react": "16.3.2",
|
|
19
|
+
"@testing-library/user-event": "14.6.1",
|
|
20
|
+
"@types/node": "22.19.21",
|
|
21
|
+
"@types/react": "19.2.17",
|
|
22
|
+
"@vitejs/plugin-react": "4.7.0",
|
|
23
|
+
"@vitest/coverage-v8": "3.2.6",
|
|
24
|
+
"jsdom": "25.0.1",
|
|
25
|
+
"react": "19.2.7",
|
|
26
|
+
"react-dom": "19.2.7",
|
|
27
|
+
"typescript": "5.6.3",
|
|
28
|
+
"vitest": "3.2.6"
|
|
29
|
+
},
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/codeshift-ai-solutions/minlang-core.git"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist"
|
|
40
|
+
],
|
|
41
|
+
"scripts": {
|
|
42
|
+
"typecheck": "tsc -p tsconfig.json",
|
|
43
|
+
"test": "vitest run",
|
|
44
|
+
"test:coverage": "vitest run --coverage",
|
|
45
|
+
"build": "tsc -p tsconfig.build.json"
|
|
46
|
+
}
|
|
47
|
+
}
|