@nsail/ui 0.0.0-stage → 0.1.135
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/app.d.ts +12 -0
- package/dist/app.js +12 -0
- package/dist/button.d.ts +8 -0
- package/dist/button.js +6 -0
- package/dist/dialog.d.ts +5 -0
- package/dist/dialog.js +33 -0
- package/dist/field.d.ts +10 -0
- package/dist/field.js +60 -0
- package/dist/form.d.ts +14 -0
- package/dist/form.js +33 -0
- package/dist/format.d.ts +3 -0
- package/dist/format.js +26 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +14 -0
- package/dist/load.d.ts +10 -0
- package/dist/load.js +16 -0
- package/dist/ns.css +339 -0
- package/dist/page.d.ts +8 -0
- package/dist/page.js +13 -0
- package/dist/pager.d.ts +9 -0
- package/dist/pager.js +12 -0
- package/dist/search.d.ts +10 -0
- package/dist/search.js +19 -0
- package/dist/select.d.ts +12 -0
- package/dist/select.js +14 -0
- package/dist/table.d.ts +14 -0
- package/dist/table.js +17 -0
- package/package.json +43 -4
- package/README.md +0 -3
package/dist/app.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { type Mediator } from "@nsail/stack";
|
|
3
|
+
export interface NsAppProps {
|
|
4
|
+
mediator: Mediator;
|
|
5
|
+
language: string;
|
|
6
|
+
/** The app's catalog for a language, merged over the Stack's. */
|
|
7
|
+
strings?: (language: string) => Promise<Record<string, string>>;
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
/** The app's root: the stack's providers, the catalog read before the first paint, and the one
|
|
11
|
+
* confirmation surface. */
|
|
12
|
+
export declare function NsApp({ mediator, language, strings, children }: NsAppProps): import("react").JSX.Element;
|
package/dist/app.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { StackProvider, useCatalog } from "@nsail/stack";
|
|
3
|
+
import { NsDialogHost } from "./dialog";
|
|
4
|
+
/** The app's root: the stack's providers, the catalog read before the first paint, and the one
|
|
5
|
+
* confirmation surface. */
|
|
6
|
+
export function NsApp({ mediator, language, strings, children }) {
|
|
7
|
+
const catalog = useCatalog(language, strings);
|
|
8
|
+
if (!catalog.strings) {
|
|
9
|
+
return _jsx("div", { className: "ns-boot", "aria-busy": "true" });
|
|
10
|
+
}
|
|
11
|
+
return (_jsxs(StackProvider, { mediator: mediator, strings: catalog.strings, children: [catalog.failed && _jsx("div", { className: "ns-banner ns-banner-error", children: catalog.strings.translate("Common.UnhandledError") }), _jsx(NsDialogHost, { children: children })] }));
|
|
12
|
+
}
|
package/dist/button.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ButtonHTMLAttributes } from "react";
|
|
2
|
+
export interface NsButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> {
|
|
3
|
+
/** A string key, never a sentence: the label is the catalog's. */
|
|
4
|
+
text: string;
|
|
5
|
+
variant?: "default" | "primary" | "danger" | "quiet";
|
|
6
|
+
busy?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function NsButton({ text, variant, busy, type, disabled, className, ...rest }: NsButtonProps): import("react").JSX.Element;
|
package/dist/button.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useStrings } from "@nsail/stack";
|
|
3
|
+
export function NsButton({ text, variant = "default", busy = false, type = "button", disabled, className, ...rest }) {
|
|
4
|
+
const strings = useStrings();
|
|
5
|
+
return (_jsx("button", { ...rest, type: type, disabled: disabled || busy, "aria-busy": busy || undefined, className: `ns-button ns-button-${variant}${className ? ` ${className}` : ""}`, children: strings.translate(text) }));
|
|
6
|
+
}
|
package/dist/dialog.d.ts
ADDED
package/dist/dialog.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) 2026 Leonardo Porro and Emmanuel Arias. https://github.com/nsail-ar/nsail-stack
|
|
4
|
+
import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
|
|
5
|
+
import { NsButton } from "./button";
|
|
6
|
+
const DialogContext = createContext(null);
|
|
7
|
+
// One confirmation surface for the whole app, a native <dialog>: focus trapping, Escape and the
|
|
8
|
+
// backdrop come from the browser instead of from code this package would own.
|
|
9
|
+
export function NsDialogHost({ children }) {
|
|
10
|
+
const [pending, setPending] = useState(null);
|
|
11
|
+
const dialog = useRef(null);
|
|
12
|
+
const confirm = useCallback((text, danger = false) => {
|
|
13
|
+
return new Promise((resolve) => setPending({ text, danger, resolve }));
|
|
14
|
+
}, []);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (pending && dialog.current && !dialog.current.open) {
|
|
17
|
+
dialog.current.showModal();
|
|
18
|
+
}
|
|
19
|
+
}, [pending]);
|
|
20
|
+
function close(confirmed) {
|
|
21
|
+
pending?.resolve(confirmed);
|
|
22
|
+
dialog.current?.close();
|
|
23
|
+
setPending(null);
|
|
24
|
+
}
|
|
25
|
+
return (_jsxs(DialogContext.Provider, { value: confirm, children: [children, _jsx("dialog", { ref: dialog, className: "ns-dialog", onCancel: () => close(false), children: pending && (_jsxs(_Fragment, { children: [_jsx("p", { children: pending.text }), _jsxs("div", { className: "ns-actions", children: [_jsx(NsButton, { text: "Actions.Cancel", onClick: () => close(false) }), _jsx(NsButton, { text: "Actions.Confirm", variant: pending.danger ? "danger" : "primary", onClick: () => close(true), autoFocus: true })] })] })) })] }));
|
|
26
|
+
}
|
|
27
|
+
export function useConfirm() {
|
|
28
|
+
const confirm = useContext(DialogContext);
|
|
29
|
+
if (!confirm) {
|
|
30
|
+
throw new Error("useConfirm needs an <NsApp> above it.");
|
|
31
|
+
}
|
|
32
|
+
return confirm;
|
|
33
|
+
}
|
package/dist/field.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface NsFieldProps<T> {
|
|
2
|
+
name: keyof T & string;
|
|
3
|
+
multiline?: boolean;
|
|
4
|
+
autoFocus?: boolean;
|
|
5
|
+
/** A string key replacing the one the field's own key derives. */
|
|
6
|
+
label?: string;
|
|
7
|
+
}
|
|
8
|
+
/** One member of the form's message, drawn from its descriptor: the input fits the wire kind,
|
|
9
|
+
* the label is the member's key, the bounds are the message's own DataAnnotations. */
|
|
10
|
+
export declare function NsField<T>({ name, multiline, autoFocus, label }: NsFieldProps<T>): import("react").JSX.Element;
|
package/dist/field.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) 2026 Leonardo Porro and Emmanuel Arias. https://github.com/nsail-ar/nsail-stack
|
|
4
|
+
import { useId } from "react";
|
|
5
|
+
import { memberOf, useStrings } from "@nsail/stack";
|
|
6
|
+
import { useFormContext } from "./form";
|
|
7
|
+
const inputTypes = { email: "email", phone: "tel", url: "url" };
|
|
8
|
+
/** One member of the form's message, drawn from its descriptor: the input fits the wire kind,
|
|
9
|
+
* the label is the member's key, the bounds are the message's own DataAnnotations. */
|
|
10
|
+
export function NsField({ name, multiline = false, autoFocus, label }) {
|
|
11
|
+
const form = useFormContext();
|
|
12
|
+
const strings = useStrings();
|
|
13
|
+
const id = useId();
|
|
14
|
+
const field = form.message.fields[name];
|
|
15
|
+
const value = form.value[name];
|
|
16
|
+
const issues = form.issues(name);
|
|
17
|
+
const caption = strings.translate(label ?? field.key, memberOf(field));
|
|
18
|
+
const invalid = issues.length > 0;
|
|
19
|
+
function set(next) {
|
|
20
|
+
form.set(name, next);
|
|
21
|
+
}
|
|
22
|
+
function text(event) {
|
|
23
|
+
const raw = event.target.value;
|
|
24
|
+
set(raw === "" && field.nullable ? null : raw);
|
|
25
|
+
}
|
|
26
|
+
function number(event) {
|
|
27
|
+
const raw = event.target.value;
|
|
28
|
+
set(raw === "" ? (field.nullable ? null : undefined) : Number(raw));
|
|
29
|
+
}
|
|
30
|
+
const common = {
|
|
31
|
+
id,
|
|
32
|
+
name,
|
|
33
|
+
autoFocus,
|
|
34
|
+
"aria-invalid": invalid || undefined,
|
|
35
|
+
"aria-describedby": invalid ? `${id}-issues` : undefined,
|
|
36
|
+
required: field.required,
|
|
37
|
+
};
|
|
38
|
+
let control;
|
|
39
|
+
switch (field.kind) {
|
|
40
|
+
case "boolean":
|
|
41
|
+
control = _jsx("input", { ...common, type: "checkbox", checked: value === true, onChange: (e) => set(e.target.checked) });
|
|
42
|
+
break;
|
|
43
|
+
case "integer":
|
|
44
|
+
case "number":
|
|
45
|
+
control = (_jsx("input", { ...common, type: "number", min: field.min, max: field.max, step: field.kind === "integer" ? 1 : "any", value: value === null || value === undefined ? "" : String(value), onChange: number }));
|
|
46
|
+
break;
|
|
47
|
+
case "enum":
|
|
48
|
+
control = (_jsxs("select", { ...common, value: value ?? "", onChange: text, children: [(field.nullable || value === undefined) && _jsx("option", { value: "" }), field.enum?.values.map((option) => (_jsx("option", { value: option, children: strings.enumValue(field.enum, option) }, option)))] }));
|
|
49
|
+
break;
|
|
50
|
+
case "date":
|
|
51
|
+
control = _jsx("input", { ...common, type: "date", value: value ?? "", onChange: text });
|
|
52
|
+
break;
|
|
53
|
+
case "time":
|
|
54
|
+
control = _jsx("input", { ...common, type: "time", value: value ?? "", onChange: text });
|
|
55
|
+
break;
|
|
56
|
+
default:
|
|
57
|
+
control = multiline ? (_jsx("textarea", { ...common, rows: 4, maxLength: field.maxLength, value: value ?? "", onChange: text })) : (_jsx("input", { ...common, type: (field.format && inputTypes[field.format]) || "text", maxLength: field.maxLength, value: value ?? "", onChange: text }));
|
|
58
|
+
}
|
|
59
|
+
return (_jsxs("div", { className: `ns-field ns-field-${field.kind}${invalid ? " ns-field-invalid" : ""}`, children: [_jsxs("label", { htmlFor: id, children: [caption, field.required && _jsx("span", { className: "ns-required", "aria-hidden": "true", children: " *" })] }), control, invalid && (_jsx("div", { id: `${id}-issues`, className: "ns-issues", children: issues.map((issue, index) => (_jsx("div", { children: strings.issue(issue) }, index))) }))] }));
|
|
60
|
+
}
|
package/dist/form.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type Form } from "@nsail/stack";
|
|
3
|
+
export declare function useFormContext<T>(): Form<T>;
|
|
4
|
+
/** "Saved" beside the form's buttons, for as long as nothing has changed since the send. */
|
|
5
|
+
export declare function NsSaved(): import("react").JSX.Element | null;
|
|
6
|
+
export interface NsFormProps<T> {
|
|
7
|
+
form: Form<T>;
|
|
8
|
+
onSubmit: (value: T) => Promise<void>;
|
|
9
|
+
/** The screen's first read, run again: offered only while the refusal is a Conflict, because
|
|
10
|
+
* re-applying the edit over what someone else saved is the user's act, never a merge. */
|
|
11
|
+
onReload?: () => void;
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
}
|
|
14
|
+
export declare function NsForm<T>({ form, onSubmit, onReload, children }: NsFormProps<T>): import("react").JSX.Element;
|
package/dist/form.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) 2026 Leonardo Porro and Emmanuel Arias. https://github.com/nsail-ar/nsail-stack
|
|
4
|
+
import { createContext, useContext } from "react";
|
|
5
|
+
import { memberOf, useStrings } from "@nsail/stack";
|
|
6
|
+
import { NsButton } from "./button";
|
|
7
|
+
const FormContext = createContext(null);
|
|
8
|
+
export function useFormContext() {
|
|
9
|
+
const form = useContext(FormContext);
|
|
10
|
+
if (!form) {
|
|
11
|
+
throw new Error("An NsField needs an <NsForm> around it.");
|
|
12
|
+
}
|
|
13
|
+
return form;
|
|
14
|
+
}
|
|
15
|
+
/** "Saved" beside the form's buttons, for as long as nothing has changed since the send. */
|
|
16
|
+
export function NsSaved() {
|
|
17
|
+
const form = useFormContext();
|
|
18
|
+
const strings = useStrings();
|
|
19
|
+
return form.saved ? (_jsx("span", { className: "ns-saved", role: "status", children: strings.translate("Common.Saved") })) : null;
|
|
20
|
+
}
|
|
21
|
+
export function NsForm({ form, onSubmit, onReload, children }) {
|
|
22
|
+
const strings = useStrings();
|
|
23
|
+
function handle(event) {
|
|
24
|
+
event.preventDefault();
|
|
25
|
+
void form.submit(onSubmit);
|
|
26
|
+
}
|
|
27
|
+
// A refusal no field can show — a missing row, a rule about the whole act, a lost
|
|
28
|
+
// connection — is drawn above the fields instead of vanishing.
|
|
29
|
+
const members = new Set(Object.values(form.message.fields).map(memberOf));
|
|
30
|
+
const loose = form.problem?.issues.filter((issue) => !issue.source || !members.has(issue.source)) ?? [];
|
|
31
|
+
const banner = form.problem && (form.problem.code !== "InvalidModel" || loose.length > 0);
|
|
32
|
+
return (_jsx(FormContext.Provider, { value: form, children: _jsxs("form", { className: "ns-form", onSubmit: handle, noValidate: true, children: [banner && form.problem && (_jsxs("div", { className: "ns-banner ns-banner-error", role: "alert", children: [_jsx("strong", { children: strings.problem(form.problem) }), loose.map((issue, index) => (_jsx("div", { children: strings.issue(issue) }, index))), onReload && form.problem.code === "Conflict" && _jsx(NsButton, { text: "Common.Reload", onClick: onReload })] })), children] }) }));
|
|
33
|
+
}
|
package/dist/format.d.ts
ADDED
package/dist/format.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2026 Leonardo Porro and Emmanuel Arias. https://github.com/nsail-ar/nsail-stack
|
|
3
|
+
/** A wire value as a person reads it: the member's kind decides, the language formats. */
|
|
4
|
+
export function display(strings, field, value) {
|
|
5
|
+
if (value === null || value === undefined || value === "") {
|
|
6
|
+
return "";
|
|
7
|
+
}
|
|
8
|
+
switch (field.kind) {
|
|
9
|
+
case "enum":
|
|
10
|
+
return field.enum ? strings.enumValue(field.enum, String(value)) : String(value);
|
|
11
|
+
case "boolean":
|
|
12
|
+
return strings.translate(value ? "Common.Yes" : "Common.No");
|
|
13
|
+
case "date": {
|
|
14
|
+
// A business date carries no zone; read as UTC midnight it cannot slide a day.
|
|
15
|
+
const date = new Date(`${String(value)}T00:00:00Z`);
|
|
16
|
+
return date.toLocaleDateString(strings.language, { timeZone: "UTC" });
|
|
17
|
+
}
|
|
18
|
+
case "datetime":
|
|
19
|
+
return new Date(String(value)).toLocaleString(strings.language, { dateStyle: "short", timeStyle: "short" });
|
|
20
|
+
case "integer":
|
|
21
|
+
case "number":
|
|
22
|
+
return Number(value).toLocaleString(strings.language);
|
|
23
|
+
default:
|
|
24
|
+
return String(value);
|
|
25
|
+
}
|
|
26
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from "./app";
|
|
2
|
+
export * from "./button";
|
|
3
|
+
export * from "./dialog";
|
|
4
|
+
export * from "./load";
|
|
5
|
+
export * from "./form";
|
|
6
|
+
export * from "./field";
|
|
7
|
+
export * from "./format";
|
|
8
|
+
export * from "./table";
|
|
9
|
+
export * from "./pager";
|
|
10
|
+
export * from "./page";
|
|
11
|
+
export * from "./search";
|
|
12
|
+
export * from "./select";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2026 Leonardo Porro and Emmanuel Arias. https://github.com/nsail-ar/nsail-stack
|
|
3
|
+
export * from "./app";
|
|
4
|
+
export * from "./button";
|
|
5
|
+
export * from "./dialog";
|
|
6
|
+
export * from "./load";
|
|
7
|
+
export * from "./form";
|
|
8
|
+
export * from "./field";
|
|
9
|
+
export * from "./format";
|
|
10
|
+
export * from "./table";
|
|
11
|
+
export * from "./pager";
|
|
12
|
+
export * from "./page";
|
|
13
|
+
export * from "./search";
|
|
14
|
+
export * from "./select";
|
package/dist/load.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { type Load } from "@nsail/stack";
|
|
3
|
+
export interface NsLoadProps<T> {
|
|
4
|
+
load: Load<T>;
|
|
5
|
+
children: (data: T) => ReactNode;
|
|
6
|
+
}
|
|
7
|
+
/** A region that owns its read: a spinner the first time, the last answer (dimmed) while it
|
|
8
|
+
* re-reads, and a failure in place with a retry — never a toast, never an empty state that
|
|
9
|
+
* lies about a read that failed. */
|
|
10
|
+
export declare function NsLoad<T>({ load, children }: NsLoadProps<T>): import("react").JSX.Element;
|
package/dist/load.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useStrings } from "@nsail/stack";
|
|
3
|
+
import { NsButton } from "./button";
|
|
4
|
+
/** A region that owns its read: a spinner the first time, the last answer (dimmed) while it
|
|
5
|
+
* re-reads, and a failure in place with a retry — never a toast, never an empty state that
|
|
6
|
+
* lies about a read that failed. */
|
|
7
|
+
export function NsLoad({ load, children }) {
|
|
8
|
+
const strings = useStrings();
|
|
9
|
+
if (load.state === "failed") {
|
|
10
|
+
return (_jsxs("div", { className: "ns-failure", role: "alert", children: [_jsx("strong", { children: strings.problem(load.problem) }), load.problem.issues.map((issue, index) => (_jsx("div", { children: strings.issue(issue) }, index))), _jsx(NsButton, { text: "Common.Retry", onClick: load.reload })] }));
|
|
11
|
+
}
|
|
12
|
+
if (load.data === undefined) {
|
|
13
|
+
return _jsxs("div", { className: "ns-loading", "aria-busy": "true", children: [strings.translate("Common.Loading"), "\u2026"] });
|
|
14
|
+
}
|
|
15
|
+
return _jsx("div", { className: load.state === "loading" ? "ns-region ns-stale" : "ns-region", children: children(load.data) });
|
|
16
|
+
}
|
package/dist/ns.css
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--ns-font: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
3
|
+
--ns-bg: #f6f7f9;
|
|
4
|
+
--ns-surface: #ffffff;
|
|
5
|
+
--ns-ink: #1d2433;
|
|
6
|
+
--ns-muted: #5d6677;
|
|
7
|
+
--ns-line: #e1e4ea;
|
|
8
|
+
--ns-primary: #2f5bd3;
|
|
9
|
+
--ns-primary-ink: #ffffff;
|
|
10
|
+
--ns-danger: #c4342d;
|
|
11
|
+
--ns-danger-ink: #ffffff;
|
|
12
|
+
--ns-error-bg: #fdeceb;
|
|
13
|
+
--ns-hover: #f0f3f8;
|
|
14
|
+
--ns-radius: 8px;
|
|
15
|
+
--ns-shadow: 0 1px 2px rgb(16 24 40 / 6%), 0 1px 3px rgb(16 24 40 / 10%);
|
|
16
|
+
color-scheme: light;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@media (prefers-color-scheme: dark) {
|
|
20
|
+
:root {
|
|
21
|
+
--ns-bg: #12151b;
|
|
22
|
+
--ns-surface: #1b1f27;
|
|
23
|
+
--ns-ink: #e6e9ef;
|
|
24
|
+
--ns-muted: #9aa3b2;
|
|
25
|
+
--ns-line: #2c323d;
|
|
26
|
+
--ns-primary: #6f93ff;
|
|
27
|
+
--ns-primary-ink: #0d1120;
|
|
28
|
+
--ns-danger: #ff6b63;
|
|
29
|
+
--ns-danger-ink: #1a0b0a;
|
|
30
|
+
--ns-error-bg: #3a1d1c;
|
|
31
|
+
--ns-hover: #232935;
|
|
32
|
+
color-scheme: dark;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
*,
|
|
37
|
+
*::before,
|
|
38
|
+
*::after {
|
|
39
|
+
box-sizing: border-box;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
body {
|
|
43
|
+
margin: 0;
|
|
44
|
+
font-family: var(--ns-font);
|
|
45
|
+
font-size: 15px;
|
|
46
|
+
line-height: 1.45;
|
|
47
|
+
background: var(--ns-bg);
|
|
48
|
+
color: var(--ns-ink);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.ns-boot {
|
|
52
|
+
min-height: 100vh;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.ns-page {
|
|
56
|
+
max-width: 1080px;
|
|
57
|
+
margin: 0 auto;
|
|
58
|
+
padding: 24px 16px 48px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.ns-page-header {
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
justify-content: space-between;
|
|
65
|
+
gap: 12px;
|
|
66
|
+
flex-wrap: wrap;
|
|
67
|
+
margin-bottom: 16px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.ns-page-header h1 {
|
|
71
|
+
margin: 0;
|
|
72
|
+
font-size: 22px;
|
|
73
|
+
font-weight: 650;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.ns-actions {
|
|
77
|
+
display: flex;
|
|
78
|
+
gap: 8px;
|
|
79
|
+
flex-wrap: wrap;
|
|
80
|
+
align-items: center;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.ns-toolbar {
|
|
84
|
+
display: flex;
|
|
85
|
+
gap: 8px;
|
|
86
|
+
flex-wrap: wrap;
|
|
87
|
+
align-items: center;
|
|
88
|
+
margin-bottom: 12px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.ns-card {
|
|
92
|
+
background: var(--ns-surface);
|
|
93
|
+
border: 1px solid var(--ns-line);
|
|
94
|
+
border-radius: var(--ns-radius);
|
|
95
|
+
box-shadow: var(--ns-shadow);
|
|
96
|
+
padding: 16px;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.ns-button {
|
|
100
|
+
font: inherit;
|
|
101
|
+
font-weight: 550;
|
|
102
|
+
border: 1px solid var(--ns-line);
|
|
103
|
+
border-radius: var(--ns-radius);
|
|
104
|
+
background: var(--ns-surface);
|
|
105
|
+
color: var(--ns-ink);
|
|
106
|
+
padding: 7px 14px;
|
|
107
|
+
cursor: pointer;
|
|
108
|
+
white-space: nowrap;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.ns-button:hover:not(:disabled) {
|
|
112
|
+
background: var(--ns-hover);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.ns-button:disabled {
|
|
116
|
+
opacity: 0.55;
|
|
117
|
+
cursor: default;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.ns-button-primary {
|
|
121
|
+
background: var(--ns-primary);
|
|
122
|
+
border-color: var(--ns-primary);
|
|
123
|
+
color: var(--ns-primary-ink);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.ns-button-primary:hover:not(:disabled) {
|
|
127
|
+
background: var(--ns-primary);
|
|
128
|
+
filter: brightness(1.08);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.ns-button-danger {
|
|
132
|
+
background: var(--ns-danger);
|
|
133
|
+
border-color: var(--ns-danger);
|
|
134
|
+
color: var(--ns-danger-ink);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.ns-button-danger:hover:not(:disabled) {
|
|
138
|
+
background: var(--ns-danger);
|
|
139
|
+
filter: brightness(1.08);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.ns-button-quiet {
|
|
143
|
+
border-color: transparent;
|
|
144
|
+
background: transparent;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.ns-button[aria-busy="true"] {
|
|
148
|
+
cursor: progress;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.ns-search,
|
|
152
|
+
.ns-field input:not([type="checkbox"]),
|
|
153
|
+
.ns-field select,
|
|
154
|
+
.ns-field textarea,
|
|
155
|
+
.ns-select {
|
|
156
|
+
font: inherit;
|
|
157
|
+
color: inherit;
|
|
158
|
+
background: var(--ns-surface);
|
|
159
|
+
border: 1px solid var(--ns-line);
|
|
160
|
+
border-radius: var(--ns-radius);
|
|
161
|
+
padding: 7px 10px;
|
|
162
|
+
min-width: 0;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.ns-search {
|
|
166
|
+
flex: 1 1 240px;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.ns-table-scroll {
|
|
170
|
+
overflow-x: auto;
|
|
171
|
+
background: var(--ns-surface);
|
|
172
|
+
border: 1px solid var(--ns-line);
|
|
173
|
+
border-radius: var(--ns-radius);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.ns-table {
|
|
177
|
+
width: 100%;
|
|
178
|
+
border-collapse: collapse;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.ns-table th,
|
|
182
|
+
.ns-table td {
|
|
183
|
+
text-align: left;
|
|
184
|
+
padding: 10px 12px;
|
|
185
|
+
border-bottom: 1px solid var(--ns-line);
|
|
186
|
+
white-space: nowrap;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.ns-table th {
|
|
190
|
+
font-size: 13px;
|
|
191
|
+
font-weight: 600;
|
|
192
|
+
color: var(--ns-muted);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.ns-table tbody tr:last-child td {
|
|
196
|
+
border-bottom: none;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.ns-table .ns-col-integer,
|
|
200
|
+
.ns-table .ns-col-number {
|
|
201
|
+
text-align: right;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.ns-table .ns-col-actions {
|
|
205
|
+
text-align: right;
|
|
206
|
+
width: 1%;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.ns-clickable {
|
|
210
|
+
cursor: pointer;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.ns-clickable:hover {
|
|
214
|
+
background: var(--ns-hover);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.ns-pager {
|
|
218
|
+
display: flex;
|
|
219
|
+
gap: 4px;
|
|
220
|
+
align-items: center;
|
|
221
|
+
justify-content: flex-end;
|
|
222
|
+
padding-top: 8px;
|
|
223
|
+
color: var(--ns-muted);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.ns-pager span {
|
|
227
|
+
margin-right: 8px;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.ns-empty,
|
|
231
|
+
.ns-loading {
|
|
232
|
+
padding: 32px 16px;
|
|
233
|
+
text-align: center;
|
|
234
|
+
color: var(--ns-muted);
|
|
235
|
+
background: var(--ns-surface);
|
|
236
|
+
border: 1px dashed var(--ns-line);
|
|
237
|
+
border-radius: var(--ns-radius);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.ns-stale {
|
|
241
|
+
opacity: 0.6;
|
|
242
|
+
transition: opacity 0.15s;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.ns-failure,
|
|
246
|
+
.ns-banner-error {
|
|
247
|
+
display: grid;
|
|
248
|
+
gap: 4px;
|
|
249
|
+
justify-items: start;
|
|
250
|
+
padding: 12px 14px;
|
|
251
|
+
border-radius: var(--ns-radius);
|
|
252
|
+
background: var(--ns-error-bg);
|
|
253
|
+
color: var(--ns-ink);
|
|
254
|
+
border: 1px solid var(--ns-danger);
|
|
255
|
+
margin-bottom: 12px;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.ns-form {
|
|
259
|
+
display: grid;
|
|
260
|
+
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
|
261
|
+
gap: 14px 16px;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.ns-form > .ns-banner-error,
|
|
265
|
+
.ns-form > .ns-actions,
|
|
266
|
+
.ns-form > .ns-field-wide {
|
|
267
|
+
grid-column: 1 / -1;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.ns-field {
|
|
271
|
+
display: grid;
|
|
272
|
+
gap: 4px;
|
|
273
|
+
align-content: start;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.ns-field label {
|
|
277
|
+
font-size: 13px;
|
|
278
|
+
font-weight: 600;
|
|
279
|
+
color: var(--ns-muted);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.ns-field-boolean {
|
|
283
|
+
grid-template-columns: auto 1fr;
|
|
284
|
+
align-items: center;
|
|
285
|
+
align-self: end;
|
|
286
|
+
padding-bottom: 8px;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.ns-field-boolean label {
|
|
290
|
+
order: 2;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.ns-field-boolean .ns-issues {
|
|
294
|
+
grid-column: 1 / -1;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.ns-field-invalid input,
|
|
298
|
+
.ns-field-invalid select,
|
|
299
|
+
.ns-field-invalid textarea {
|
|
300
|
+
border-color: var(--ns-danger) !important;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.ns-issues {
|
|
304
|
+
font-size: 13px;
|
|
305
|
+
color: var(--ns-danger);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.ns-saved {
|
|
309
|
+
color: var(--ns-muted);
|
|
310
|
+
font-size: 13px;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.ns-required {
|
|
314
|
+
color: var(--ns-danger);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.ns-dialog {
|
|
318
|
+
border: 1px solid var(--ns-line);
|
|
319
|
+
border-radius: var(--ns-radius);
|
|
320
|
+
background: var(--ns-surface);
|
|
321
|
+
color: var(--ns-ink);
|
|
322
|
+
box-shadow: var(--ns-shadow);
|
|
323
|
+
padding: 20px;
|
|
324
|
+
width: min(420px, calc(100vw - 32px));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.ns-dialog::backdrop {
|
|
328
|
+
background: rgb(0 0 0 / 35%);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.ns-dialog .ns-actions {
|
|
332
|
+
justify-content: flex-end;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
@media (max-width: 600px) {
|
|
336
|
+
.ns-table .ns-hide-narrow {
|
|
337
|
+
display: none;
|
|
338
|
+
}
|
|
339
|
+
}
|
package/dist/page.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export interface NsPageProps {
|
|
3
|
+
/** A string key. */
|
|
4
|
+
title: string;
|
|
5
|
+
actions?: ReactNode;
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
export declare function NsPage({ title, actions, children }: NsPageProps): import("react").JSX.Element;
|
package/dist/page.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) 2026 Leonardo Porro and Emmanuel Arias. https://github.com/nsail-ar/nsail-stack
|
|
4
|
+
import { useEffect } from "react";
|
|
5
|
+
import { useStrings } from "@nsail/stack";
|
|
6
|
+
export function NsPage({ title, actions, children }) {
|
|
7
|
+
const strings = useStrings();
|
|
8
|
+
const text = strings.translate(title);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
document.title = text;
|
|
11
|
+
}, [text]);
|
|
12
|
+
return (_jsxs("main", { className: "ns-page", children: [_jsxs("header", { className: "ns-page-header", children: [_jsx("h1", { children: text }), actions && _jsx("div", { className: "ns-actions", children: actions })] }), children] }));
|
|
13
|
+
}
|
package/dist/pager.d.ts
ADDED
package/dist/pager.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) 2026 Leonardo Porro and Emmanuel Arias. https://github.com/nsail-ar/nsail-stack
|
|
4
|
+
import { useStrings } from "@nsail/stack";
|
|
5
|
+
import { NsButton } from "./button";
|
|
6
|
+
export function NsPager({ page, onPage }) {
|
|
7
|
+
const strings = useStrings();
|
|
8
|
+
const from = page.totalCount === 0 ? 0 : page.pageIndex * page.pageSize + 1;
|
|
9
|
+
const to = Math.min((page.pageIndex + 1) * page.pageSize, page.totalCount);
|
|
10
|
+
const last = Math.max(0, Math.ceil(page.totalCount / page.pageSize) - 1);
|
|
11
|
+
return (_jsxs("nav", { className: "ns-pager", children: [_jsx("span", { children: strings.format("Mud.MudDataGridPager_InfoFormat", [String(from), String(to), String(page.totalCount)]) }), _jsx(NsButton, { text: "Mud.MudTablePager_PreviousPage", variant: "quiet", disabled: page.pageIndex <= 0, onClick: () => onPage(page.pageIndex - 1) }), _jsx(NsButton, { text: "Mud.MudTablePager_NextPage", variant: "quiet", disabled: page.pageIndex >= last, onClick: () => onPage(page.pageIndex + 1) })] }));
|
|
12
|
+
}
|
package/dist/search.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Field } from "@nsail/stack";
|
|
2
|
+
export interface NsSearchProps {
|
|
3
|
+
/** The message member the term is sent as: its key is the label, its bound the input's. */
|
|
4
|
+
field: Field;
|
|
5
|
+
value: string | null | undefined;
|
|
6
|
+
onSearch: (term: string | null) => void;
|
|
7
|
+
delay?: number;
|
|
8
|
+
}
|
|
9
|
+
/** A search box that sends once the typing stops, not on every key. */
|
|
10
|
+
export declare function NsSearch({ field, value, onSearch, delay }: NsSearchProps): import("react").JSX.Element;
|
package/dist/search.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) 2026 Leonardo Porro and Emmanuel Arias. https://github.com/nsail-ar/nsail-stack
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
import { useStrings } from "@nsail/stack";
|
|
6
|
+
/** A search box that sends once the typing stops, not on every key. */
|
|
7
|
+
export function NsSearch({ field, value, onSearch, delay = 300 }) {
|
|
8
|
+
const strings = useStrings();
|
|
9
|
+
const [term, setTerm] = useState(value ?? "");
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const trimmed = term.trim();
|
|
12
|
+
if (trimmed === (value ?? "")) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const timer = setTimeout(() => onSearch(trimmed.length > 0 ? trimmed : null), delay);
|
|
16
|
+
return () => clearTimeout(timer);
|
|
17
|
+
}, [term, value, onSearch, delay]);
|
|
18
|
+
return (_jsx("input", { className: "ns-search", type: "search", "aria-label": strings.translate(field.key), placeholder: strings.translate("Common.Search"), maxLength: field.maxLength, value: term, onChange: (event) => setTerm(event.target.value) }));
|
|
19
|
+
}
|
package/dist/select.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type Field } from "@nsail/stack";
|
|
2
|
+
export interface NsEnumSelectProps<TValue extends string> {
|
|
3
|
+
/** A member whose kind is enum: its key labels the select, its values are the options. */
|
|
4
|
+
field: Field;
|
|
5
|
+
value: TValue | null | undefined;
|
|
6
|
+
onChange: (value: TValue | null) => void;
|
|
7
|
+
/** A string key: what "no value" reads as, offered when the member is nullable. */
|
|
8
|
+
none?: string;
|
|
9
|
+
}
|
|
10
|
+
/** A filter or a pick over an enum member, outside a form: the options and their words come
|
|
11
|
+
* from the descriptor, so the page never lists them itself. */
|
|
12
|
+
export declare function NsEnumSelect<TValue extends string>({ field, value, onChange, none }: NsEnumSelectProps<TValue>): import("react").JSX.Element;
|
package/dist/select.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) 2026 Leonardo Porro and Emmanuel Arias. https://github.com/nsail-ar/nsail-stack
|
|
4
|
+
import { useStrings } from "@nsail/stack";
|
|
5
|
+
/** A filter or a pick over an enum member, outside a form: the options and their words come
|
|
6
|
+
* from the descriptor, so the page never lists them itself. */
|
|
7
|
+
export function NsEnumSelect({ field, value, onChange, none = "Common.All" }) {
|
|
8
|
+
const strings = useStrings();
|
|
9
|
+
if (!field.enum) {
|
|
10
|
+
throw new Error(`${field.key} is not an enum member.`);
|
|
11
|
+
}
|
|
12
|
+
const type = field.enum;
|
|
13
|
+
return (_jsxs("select", { className: "ns-select", "aria-label": strings.translate(field.key), value: value ?? "", onChange: (event) => onChange((event.target.value || null)), children: [field.nullable && _jsx("option", { value: "", children: strings.translate(none) }), type.values.map((option) => (_jsx("option", { value: option, children: strings.enumValue(type, option) }, option)))] }));
|
|
14
|
+
}
|
package/dist/table.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { type ModelType } from "@nsail/stack";
|
|
3
|
+
export interface NsTableProps<T> {
|
|
4
|
+
model: ModelType<T>;
|
|
5
|
+
rows: readonly T[];
|
|
6
|
+
columns: readonly (keyof T & string)[];
|
|
7
|
+
rowKey: keyof T & string;
|
|
8
|
+
onRowClick?: (row: T) => void;
|
|
9
|
+
actions?: (row: T) => ReactNode;
|
|
10
|
+
/** A string key: what an empty answer says. */
|
|
11
|
+
empty?: string;
|
|
12
|
+
}
|
|
13
|
+
/** Rows of a projection: each header is the member's key, each cell its value formatted by kind. */
|
|
14
|
+
export declare function NsTable<T>({ model, rows, columns, rowKey, onRowClick, actions, empty }: NsTableProps<T>): import("react").JSX.Element;
|
package/dist/table.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { memberOf, useStrings } from "@nsail/stack";
|
|
3
|
+
import { display } from "./format";
|
|
4
|
+
/** Rows of a projection: each header is the member's key, each cell its value formatted by kind. */
|
|
5
|
+
export function NsTable({ model, rows, columns, rowKey, onRowClick, actions, empty = "Common.NoRecords" }) {
|
|
6
|
+
const strings = useStrings();
|
|
7
|
+
if (rows.length === 0) {
|
|
8
|
+
return _jsx("div", { className: "ns-empty", children: strings.translate(empty) });
|
|
9
|
+
}
|
|
10
|
+
return (_jsx("div", { className: "ns-table-scroll", children: _jsxs("table", { className: "ns-table", children: [_jsx("thead", { children: _jsxs("tr", { children: [columns.map((column) => {
|
|
11
|
+
const field = model.fields[column];
|
|
12
|
+
return (_jsx("th", { className: `ns-col-${field.kind}`, children: strings.translate(field.key, memberOf(field)) }, column));
|
|
13
|
+
}), actions && _jsx("th", { className: "ns-col-actions" })] }) }), _jsx("tbody", { children: rows.map((row) => (_jsxs("tr", { className: onRowClick ? "ns-clickable" : undefined, onClick: onRowClick ? () => onRowClick(row) : undefined, children: [columns.map((column) => {
|
|
14
|
+
const field = model.fields[column];
|
|
15
|
+
return (_jsx("td", { className: `ns-col-${field.kind}`, children: display(strings, field, row[column]) }, column));
|
|
16
|
+
}), actions && (_jsx("td", { className: "ns-col-actions", onClick: (event) => event.stopPropagation(), children: actions(row) }))] }, String(row[rowKey])))) })] }) }));
|
|
17
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nsail/ui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.135",
|
|
4
|
+
"description": "The Ns* components for a React client, drawn from the descriptors an Sdk generates.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/nsail-ar/nsail-stack.git",
|
|
9
|
+
"directory": "src/Stack/React/NSail.React.Ui"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/nsail-ar/nsail-stack#readme",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"sideEffects": [
|
|
14
|
+
"*.css"
|
|
15
|
+
],
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"@nsail/source": "./src/index.ts",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./ns.css": {
|
|
23
|
+
"@nsail/source": "./src/ns.css",
|
|
24
|
+
"default": "./dist/ns.css"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"react": ">=19",
|
|
35
|
+
"react-dom": ">=19"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@nsail/stack": "0.1.135"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsc -p tsconfig.build.json && node -e \"require('fs').copyFileSync('src/ns.css', 'dist/ns.css')\"",
|
|
42
|
+
"test": "vitest run",
|
|
43
|
+
"typecheck": "tsc --noEmit"
|
|
44
|
+
}
|
|
45
|
+
}
|
package/README.md
DELETED