release: WRNexusJS 0.3.5

This commit is contained in:
2026-07-24 12:46:44 +05:30
parent 44ba847210
commit c81dedff17
2114 changed files with 65790 additions and 150559 deletions
+14 -2
View File
@@ -53,9 +53,21 @@ const components = readdirSync(componentsDir)
const slots = [...source.matchAll(/<slot(?:\s+name="([^"]+)")?/g)].map(
(match) => match[1] ?? "default",
);
const events = [
...new Set([...source.matchAll(/@([A-Za-z][A-Za-z0-9_-]*)=/g)].map((match) => match[1])),
const declaredEvents = [
...new Set(
[...source.matchAll(/@event\s+([A-Za-z][A-Za-z0-9_]*)\s*=\s*function/g)].map(
(match) => match[1],
),
),
];
const events =
declaredEvents.length > 0
? declaredEvents
: (entry?.events ?? [
...new Set(
[...source.matchAll(/@([A-Za-z][A-Za-z0-9_-]*)=/g)].map((match) => match[1]),
),
]);
return {
name,
mount: name,
+1
View File
@@ -44,6 +44,7 @@ const ASSETS: Record<string, string[]> = {
"components",
"ui.css",
"component-catalog.json",
"component-migrations.json",
"component-reference.json",
"COMPONENTS.md",
],
+448
View File
@@ -0,0 +1,448 @@
import { readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import process from "node:process";
const root = join(import.meta.dirname, "..");
const uiRoot = join(root, "packages", "ui");
const componentsDir = join(uiRoot, "components");
const preservedButton = readFileSync(join(componentsDir, "Button.wrn"), "utf8");
const preservedAdvancedSelect = readFileSync(join(componentsDir, "AdvancedSelect.wrn"), "utf8");
const groups = {
layout: [
"Container",
"Columns",
"Grid",
"LayoutSplitter",
"Typography",
"Image",
"Link",
"Divider",
"Kbd",
"CustomScrollbar",
],
base: [
"Accordion",
"Alert",
"Avatar",
"AvatarGroup",
"Badge",
"Blockquote",
"Button",
"ButtonGroup",
"Card",
"ChatBubble",
"Carousel",
"Collapse",
"DatePicker",
"DeviceFrame",
"List",
"ListGroup",
"LegendIndicator",
"Progress",
"FileUploadProgress",
"Rating",
"Skeleton",
"Spinner",
"StyledIcon",
"Toast",
"Timeline",
"TreeView",
"Marquee",
],
navigation: [
"Navbar",
"MegaMenu",
"Nav",
"Tabs",
"Sidebar",
"Scrollspy",
"Breadcrumb",
"Pagination",
"Stepper",
],
forms: [
"Input",
"InputGroup",
"Textarea",
"FileInput",
"Checkbox",
"Radio",
"Switch",
"Select",
"RangeSlider",
"ColorPicker",
"TimePicker",
],
"advanced-forms": [
"AdvancedSelect",
"Combobox",
"SearchBox",
"InputNumber",
"StrongPassword",
"TogglePassword",
"ToggleCount",
"CopyMarkup",
"PinInput",
],
overlays: ["Dropdown", "ContextMenu", "Modal", "Drawer", "Popover", "Tooltip"],
tables: ["Table"],
integrations: [
"AdvancedRangeSlider",
"AdvancedDatePicker",
"Chart",
"Clipboard",
"Confetti",
"DataMap",
"DataTable",
"DragAndDrop",
"FileUpload",
"Map",
"ToastNotifications",
"WysiwygEditor",
],
};
const displayName = (name) => name.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
const kebab = (name) => displayName(name).replace(/\s+/g, "-").toLowerCase();
function layoutComponent(name) {
if (name === "Image")
return `component Image {
props {
src = ""
alt = ""
width = ""
height = ""
loading = "lazy"
rounded = false
class = ""
}
view {
<figure class="wire-next wire-next--image {rounded ? 'wire-next--rounded' : ''} {class}">
<img src="{src}" alt="{alt}" width="{width}" height="{height}" loading="{loading}" />
<slot />
</figure>
}
}
`;
if (name === "Link")
return `component Link {
props {
label = "Link"
href = "#"
target = ""
rel = ""
external = false
class = ""
}
view {
<a {...attrs} href="{href}" target="{target}" rel="{rel}" class="wire-next wire-next--link {class}"><slot>{label}</slot>{#if external}<span aria-hidden="true">↗</span>{/if}</a>
}
}
`;
if (name === "Divider")
return `component Divider {
props {
label = ""
orientation = "horizontal"
class = ""
}
view {
<div class="wire-next wire-next--divider wire-next--divider-{orientation} {class}" role="separator" aria-orientation="{orientation}">{#if label}<span>{label}</span>{/if}</div>
}
}
`;
if (name === "Kbd")
return `component Kbd {
props {
label = "⌘ K"
class = ""
}
view { <kbd class="wire-next wire-next--kbd {class}"><slot>{label}</slot></kbd> }
}
`;
const tag = name === "Typography" ? "article" : "div";
return `component ${name} {
props {
columns = 2
gap = "md"
maxWidth = "xl"
class = ""
}
view {
<${tag} class="wire-next wire-next--${kebab(name)} wire-next--gap-{gap} wire-next--columns-{columns} wire-next--max-{maxWidth} {class}"><slot /></${tag}>
}
}
`;
}
function collectionComponent(name) {
if (name === "Accordion" || name === "Collapse")
return `component ${name} {
props {
items = []
multiple = false
class = ""
}
view {
<div class="wire-next wire-next--${kebab(name)} {class}">
{#each items as item}<details open="{item.open}"><summary>{item.label}</summary><div>{item.content}</div></details>{/each}
<slot />
</div>
}
}
`;
if (name === "Progress" || name === "FileUploadProgress")
return `component ${name} {
props {
label = "Progress"
value = 50
max = 100
showValue = true
class = ""
}
view {
<div class="wire-next wire-next--${kebab(name)} {class}">
<div class="wire-next__row"><span>{label}</span>{#if showValue}<strong>{value}%</strong>{/if}</div>
<progress value="{value}" max="{max}"></progress>
</div>
}
}
`;
if (name === "Spinner" || name === "Skeleton")
return `component ${name} {
props {
label = "Loading"
size = "md"
lines = 3
class = ""
}
view {
<div class="wire-next wire-next--${kebab(name)} wire-next--size-{size} {class}" role="status" aria-label="{label}">
{#if "${name}" === "Skeleton"}{#each Array(lines).fill(0) as line}<span></span>{/each}{:else}<i aria-hidden="true"></i>{/if}
</div>
}
}
`;
return `component ${name} {
props {
title = "${displayName(name)}"
description = ""
items = []
variant = "default"
class = ""
}
view {
<section class="wire-next wire-next--${kebab(name)} wire-next--variant-{variant} {class}">
{#if title}<strong>{title}</strong>{/if}
{#if description}<p>{description}</p>{/if}
{#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
<slot />
</section>
}
}
`;
}
function navigationComponent(name) {
return `component ${name} {
props {
label = "${displayName(name)}"
items = []
active = ""
orientation = "horizontal"
class = ""
}
view {
<nav class="wire-next wire-next--${kebab(name)} wire-next--{orientation} {class}" aria-label="{label}">
{#each items as item}<a href="{item.href}" aria-current="{item.value === active ? 'page' : ''}">{item.label}</a>{/each}
<slot />
</nav>
}
}
`;
}
function formComponent(name) {
const typeByName = {
Input: "text",
SearchBox: "search",
InputNumber: "number",
StrongPassword: "password",
TogglePassword: "password",
PinInput: "text",
DatePicker: "date",
AdvancedDatePicker: "date",
TimePicker: "time",
ColorPicker: "color",
RangeSlider: "range",
AdvancedRangeSlider: "range",
FileInput: "file",
};
if (name === "Textarea" || name === "WysiwygEditor")
return `component ${name} {
props {
label = "${displayName(name)}"
name = ""
value = ""
placeholder = ""
rows = 5
disabled = false
required = false
class = ""
}
view {
<label class="wire-next wire-next--field wire-next--${kebab(name)} {class}"><span>{label}</span><textarea {...attrs} name="{name}" placeholder="{placeholder}" rows="{rows}" disabled="{disabled}" required="{required}">{value}</textarea></label>
}
}
`;
if (name === "Checkbox" || name === "Radio" || name === "Switch")
return `component ${name} {
props {
label = "${displayName(name)}"
name = ""
value = "on"
checked = false
disabled = false
class = ""
}
view {
<label class="wire-next wire-next--choice wire-next--${kebab(name)} {class}"><input {...attrs} type="${name === "Radio" ? "radio" : "checkbox"}" name="{name}" value="{value}" checked="{checked}" disabled="{disabled}" /><span>{label}</span></label>
}
}
`;
if (name.includes("Select") || name === "Combobox")
return `component ${name} {
props {
label = "${displayName(name)}"
name = ""
value = ""
options = []
placeholder = "Select an option"
multiple = false
disabled = false
class = ""
}
view {
<label class="wire-next wire-next--field wire-next--${kebab(name)} {class}"><span>{label}</span><select {...attrs} name="{name}" multiple="{multiple}" disabled="{disabled}"><option value="">{placeholder}</option>{#each options as option}<option value="{option.value}" selected="{option.value === value}">{option.label}</option>{/each}</select></label>
}
}
`;
const inputType = typeByName[name] ?? "text";
return `component ${name} {
props {
label = "${displayName(name)}"
name = ""
value = ""
placeholder = ""
type = "${inputType}"
min = ""
max = ""
step = ""
disabled = false
required = false
class = ""
}
view {
<label class="wire-next wire-next--field wire-next--${kebab(name)} {class}"><span>{label}</span><input {...attrs} type="{type}" name="{name}" value="{value}" placeholder="{placeholder}" min="{min}" max="{max}" step="{step}" disabled="{disabled}" required="{required}" /></label>
}
}
`;
}
function overlayComponent(name) {
const modal = name === "Modal" || name === "Drawer";
return `component ${name} {
props {
title = "${displayName(name)}"
description = ""
open = false
placement = "bottom"
closeLabel = "Close"
class = ""
}
view {
<div data-show="{open}" class="wire-next wire-next--${kebab(name)} wire-next--placement-{placement} {class}" role="${modal ? "dialog" : "region"}" aria-modal="${modal}" aria-label="{title}">
<header><strong>{title}</strong><button type="button" aria-label="{closeLabel}">×</button></header>
{#if description}<p>{description}</p>{/if}
<slot />
</div>
}
}
`;
}
function dataComponent(name) {
if (name === "Table" || name === "DataTable")
return `component ${name} {
props {
caption = "${displayName(name)}"
columns = []
rows = []
striped = true
class = ""
}
view {
<div class="wire-next wire-next--table {class}"><table><caption>{caption}</caption><thead><tr>{#each columns as column}<th>{column.label}</th>{/each}</tr></thead><tbody>{#each rows as row}<tr>{#each columns as column}<td>{row[column.key]}</td>{/each}</tr>{/each}</tbody></table><slot /></div>
}
}
`;
return collectionComponent(name);
}
function sourceFor(name, category) {
if (name === "Button") return preservedButton;
if (name === "AdvancedSelect") return preservedAdvancedSelect;
if (category === "layout") return layoutComponent(name);
if (category === "navigation") return navigationComponent(name);
if (category === "forms" || category === "advanced-forms") return formComponent(name);
if (category === "overlays") return overlayComponent(name);
if (category === "tables" || category === "integrations") return dataComponent(name);
if (name === "DatePicker") return formComponent(name);
return collectionComponent(name);
}
function withUniversalDesignProps(source) {
let next = source;
if (!/^\s+color\s*=/m.test(next)) {
next = next.replace(/(\s+props\s*\{\r?\n)/, '$1 color = "primary"\n');
}
if (!/^\s+size\s*=/m.test(next)) {
next = next.replace(/(\s+props\s*\{\r?\n)/, '$1 size = "default"\n');
}
return next.replace(
/class="wire-next(?![^"]*wire-next--color-)/,
'class="wire-next wire-next--color-{color} wire-next--size-{size}',
);
}
const entries = Object.entries(groups).flatMap(([category, names]) =>
names.map((name) => ({
name,
category,
purpose: `Theme-aware, responsive ${displayName(name).toLowerCase()} component.`,
})),
);
for (const file of readdirSync(componentsDir)) {
if (file.endsWith(".wrn")) unlinkSync(join(componentsDir, file));
}
for (const entry of entries) {
writeFileSync(
join(componentsDir, `${entry.name}.wrn`),
withUniversalDesignProps(sourceFor(entry.name, entry.category)),
"utf8",
);
}
writeFileSync(
join(uiRoot, "component-catalog.json"),
`${JSON.stringify({ sourceDocuments: ["Screenshot-directed WRNexus UI catalog reset"], components: entries }, null, 2)}\n`,
);
writeFileSync(
join(uiRoot, "component-migrations.json"),
`${JSON.stringify({ generatedFrom: "full component catalog reset", removedCount: 0, replacements: {} }, null, 2)}\n`,
);
process.stdout.write(`Reset @wrnexus/ui to ${entries.length} canonical components.\n`);