@almadar/ui 5.128.0 → 5.129.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/avl/index.cjs +296 -5
- package/dist/avl/index.js +296 -5
- package/dist/components/index.cjs +299 -5
- package/dist/components/index.d.cts +119 -1
- package/dist/components/index.d.ts +119 -1
- package/dist/components/index.js +300 -6
- package/dist/providers/index.cjs +296 -5
- package/dist/providers/index.js +296 -5
- package/dist/runtime/index.cjs +296 -5
- package/dist/runtime/index.js +296 -5
- package/package.json +6 -5
- package/scripts/audit-tailwind-safelist.ts +237 -0
- package/scripts/export-static.js +61 -0
- package/scripts/generate-design-system.ts +344 -0
- package/scripts/generate-theme-from-schema.ts +480 -0
- package/scripts/generate.ts +835 -0
- package/scripts/strip-base-tokens-from-themes.mjs +179 -0
- package/scripts/suggest-components.ts +508 -0
- package/scripts/theme-contrast-audit.mjs +132 -0
- package/scripts/types.ts +282 -0
- package/themes/_contract.md +1 -1
package/dist/runtime/index.js
CHANGED
|
@@ -26804,12 +26804,12 @@ var init_MapView = __esm({
|
|
|
26804
26804
|
shadowSize: [41, 41]
|
|
26805
26805
|
});
|
|
26806
26806
|
L.Marker.prototype.options.icon = defaultIcon;
|
|
26807
|
-
const { useEffect: useEffect59, useRef:
|
|
26807
|
+
const { useEffect: useEffect59, useRef: useRef60, useCallback: useCallback87, useState: useState89 } = React82__default;
|
|
26808
26808
|
const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
|
|
26809
26809
|
const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
|
|
26810
26810
|
function MapUpdater({ centerLat, centerLng, zoom }) {
|
|
26811
26811
|
const map = useMap();
|
|
26812
|
-
const prevRef =
|
|
26812
|
+
const prevRef = useRef60({ centerLat, centerLng, zoom });
|
|
26813
26813
|
useEffect59(() => {
|
|
26814
26814
|
const prev = prevRef.current;
|
|
26815
26815
|
if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
|
|
@@ -35861,6 +35861,290 @@ var init_GraphCanvas = __esm({
|
|
|
35861
35861
|
GraphCanvas.displayName = "GraphCanvas";
|
|
35862
35862
|
}
|
|
35863
35863
|
});
|
|
35864
|
+
var ImportSourcePicker;
|
|
35865
|
+
var init_ImportSourcePicker = __esm({
|
|
35866
|
+
"components/core/molecules/import/ImportSourcePicker.tsx"() {
|
|
35867
|
+
"use client";
|
|
35868
|
+
init_Box();
|
|
35869
|
+
init_Icon();
|
|
35870
|
+
init_Typography();
|
|
35871
|
+
init_cn();
|
|
35872
|
+
ImportSourcePicker = ({
|
|
35873
|
+
sources,
|
|
35874
|
+
onSelect,
|
|
35875
|
+
onFilesSelected,
|
|
35876
|
+
title,
|
|
35877
|
+
moreSources,
|
|
35878
|
+
className
|
|
35879
|
+
}) => {
|
|
35880
|
+
const fileInputRef = useRef(null);
|
|
35881
|
+
const handlePick = (source) => {
|
|
35882
|
+
if (source.disabled) return;
|
|
35883
|
+
if (source.kind === "file") {
|
|
35884
|
+
const input = fileInputRef.current;
|
|
35885
|
+
if (!input) return;
|
|
35886
|
+
input.accept = source.accept ?? "";
|
|
35887
|
+
input.multiple = source.multiple ?? false;
|
|
35888
|
+
input.click();
|
|
35889
|
+
return;
|
|
35890
|
+
}
|
|
35891
|
+
onSelect?.(source.id);
|
|
35892
|
+
};
|
|
35893
|
+
const handleFiles = (event) => {
|
|
35894
|
+
const files = event.target.files ? Array.from(event.target.files) : [];
|
|
35895
|
+
event.target.value = "";
|
|
35896
|
+
if (files.length > 0) onFilesSelected?.(files);
|
|
35897
|
+
};
|
|
35898
|
+
return /* @__PURE__ */ jsxs(Box, { className: cn("flex flex-col gap-2", className), children: [
|
|
35899
|
+
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
35900
|
+
sources.map((source) => /* @__PURE__ */ jsxs(
|
|
35901
|
+
Box,
|
|
35902
|
+
{
|
|
35903
|
+
role: "button",
|
|
35904
|
+
tabIndex: source.disabled ? -1 : 0,
|
|
35905
|
+
"aria-disabled": source.disabled,
|
|
35906
|
+
onClick: () => handlePick(source),
|
|
35907
|
+
onKeyDown: (event) => {
|
|
35908
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
35909
|
+
event.preventDefault();
|
|
35910
|
+
handlePick(source);
|
|
35911
|
+
}
|
|
35912
|
+
},
|
|
35913
|
+
className: cn(
|
|
35914
|
+
"flex items-center gap-3 rounded-md border border-border bg-card px-4 py-3 text-left",
|
|
35915
|
+
source.disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer hover:bg-accent hover:text-accent-foreground"
|
|
35916
|
+
),
|
|
35917
|
+
children: [
|
|
35918
|
+
source.icon ? /* @__PURE__ */ jsx(Icon, { icon: source.icon, size: "md" }) : null,
|
|
35919
|
+
/* @__PURE__ */ jsxs(Box, { className: "flex flex-col", children: [
|
|
35920
|
+
/* @__PURE__ */ jsx(Typography, { variant: "label", children: source.label }),
|
|
35921
|
+
source.description ? /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: source.description }) : null
|
|
35922
|
+
] })
|
|
35923
|
+
]
|
|
35924
|
+
},
|
|
35925
|
+
source.id
|
|
35926
|
+
)),
|
|
35927
|
+
moreSources,
|
|
35928
|
+
/* @__PURE__ */ jsx(
|
|
35929
|
+
"input",
|
|
35930
|
+
{
|
|
35931
|
+
ref: fileInputRef,
|
|
35932
|
+
type: "file",
|
|
35933
|
+
className: "hidden",
|
|
35934
|
+
onChange: handleFiles,
|
|
35935
|
+
"data-testid": "import-source-file-input"
|
|
35936
|
+
}
|
|
35937
|
+
)
|
|
35938
|
+
] });
|
|
35939
|
+
};
|
|
35940
|
+
}
|
|
35941
|
+
});
|
|
35942
|
+
function formatFieldValue(value) {
|
|
35943
|
+
if (value === null) return "\u2014";
|
|
35944
|
+
if (value instanceof Date) return value.toISOString();
|
|
35945
|
+
if (Array.isArray(value)) return value.map(formatFieldValue).join(", ");
|
|
35946
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
35947
|
+
return String(value);
|
|
35948
|
+
}
|
|
35949
|
+
function unitTitle(unit) {
|
|
35950
|
+
for (const key of TITLE_KEYS) {
|
|
35951
|
+
const value = unit.fields[key];
|
|
35952
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
35953
|
+
}
|
|
35954
|
+
return unit.ref;
|
|
35955
|
+
}
|
|
35956
|
+
function fieldSummary(unit) {
|
|
35957
|
+
return Object.entries(unit.fields).filter(([key]) => !TITLE_KEYS.includes(key)).map(([key, value]) => `${key}: ${formatFieldValue(value)}`).join(" \xB7 ");
|
|
35958
|
+
}
|
|
35959
|
+
var TITLE_KEYS, ImportPreviewTree;
|
|
35960
|
+
var init_ImportPreviewTree = __esm({
|
|
35961
|
+
"components/core/molecules/import/ImportPreviewTree.tsx"() {
|
|
35962
|
+
"use client";
|
|
35963
|
+
init_Box();
|
|
35964
|
+
init_Badge();
|
|
35965
|
+
init_Button();
|
|
35966
|
+
init_Icon();
|
|
35967
|
+
init_Typography();
|
|
35968
|
+
init_cn();
|
|
35969
|
+
TITLE_KEYS = ["title", "name", "label"];
|
|
35970
|
+
ImportPreviewTree = ({
|
|
35971
|
+
units,
|
|
35972
|
+
skipped = [],
|
|
35973
|
+
entityDisplay,
|
|
35974
|
+
onConfirm,
|
|
35975
|
+
onCancel,
|
|
35976
|
+
confirmLabel = "Confirm import",
|
|
35977
|
+
cancelLabel = "Cancel",
|
|
35978
|
+
indent = 16,
|
|
35979
|
+
className
|
|
35980
|
+
}) => {
|
|
35981
|
+
const groups = /* @__PURE__ */ new Map();
|
|
35982
|
+
for (const unit of units) {
|
|
35983
|
+
const group = groups.get(unit.targetEntity);
|
|
35984
|
+
if (group) group.push(unit);
|
|
35985
|
+
else groups.set(unit.targetEntity, [unit]);
|
|
35986
|
+
}
|
|
35987
|
+
const renderUnit = (unit, childrenByParent, depth) => {
|
|
35988
|
+
const summary = fieldSummary(unit);
|
|
35989
|
+
const children = childrenByParent.get(unit.ref) ?? [];
|
|
35990
|
+
return /* @__PURE__ */ jsxs(React82__default.Fragment, { children: [
|
|
35991
|
+
/* @__PURE__ */ jsxs(
|
|
35992
|
+
Box,
|
|
35993
|
+
{
|
|
35994
|
+
className: "flex flex-col py-1",
|
|
35995
|
+
style: { paddingLeft: depth * indent },
|
|
35996
|
+
"data-testid": `import-preview-unit-${unit.ref}`,
|
|
35997
|
+
children: [
|
|
35998
|
+
/* @__PURE__ */ jsxs(Box, { className: "flex items-center gap-2", children: [
|
|
35999
|
+
/* @__PURE__ */ jsx(Icon, { icon: "file-text", size: "xs", className: "text-muted-foreground" }),
|
|
36000
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body2", children: unitTitle(unit) })
|
|
36001
|
+
] }),
|
|
36002
|
+
summary ? /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: summary }) : null
|
|
36003
|
+
]
|
|
36004
|
+
}
|
|
36005
|
+
),
|
|
36006
|
+
children.map((child) => renderUnit(child, childrenByParent, depth + 1))
|
|
36007
|
+
] }, unit.ref);
|
|
36008
|
+
};
|
|
36009
|
+
return /* @__PURE__ */ jsxs(Box, { className: cn("flex flex-col gap-4", className), children: [
|
|
36010
|
+
units.length === 0 ? /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: "No units staged." }) : null,
|
|
36011
|
+
Array.from(groups.entries()).map(([entity, groupUnits]) => {
|
|
36012
|
+
const refs = new Set(groupUnits.map((unit) => unit.ref));
|
|
36013
|
+
const childrenByParent = /* @__PURE__ */ new Map();
|
|
36014
|
+
const roots = [];
|
|
36015
|
+
for (const unit of groupUnits) {
|
|
36016
|
+
if (unit.parentRef && refs.has(unit.parentRef)) {
|
|
36017
|
+
const siblings = childrenByParent.get(unit.parentRef);
|
|
36018
|
+
if (siblings) siblings.push(unit);
|
|
36019
|
+
else childrenByParent.set(unit.parentRef, [unit]);
|
|
36020
|
+
} else {
|
|
36021
|
+
roots.push(unit);
|
|
36022
|
+
}
|
|
36023
|
+
}
|
|
36024
|
+
const label = entityDisplay[entity]?.plural ?? entity;
|
|
36025
|
+
return /* @__PURE__ */ jsxs(Box, { border: true, className: "rounded-md border-border bg-card p-3", children: [
|
|
36026
|
+
/* @__PURE__ */ jsxs(Box, { className: "flex items-center gap-2 pb-2", children: [
|
|
36027
|
+
/* @__PURE__ */ jsx(Typography, { variant: "label", children: label }),
|
|
36028
|
+
/* @__PURE__ */ jsx(Badge, { amount: groupUnits.length })
|
|
36029
|
+
] }),
|
|
36030
|
+
roots.map((unit) => renderUnit(unit, childrenByParent, 0))
|
|
36031
|
+
] }, entity);
|
|
36032
|
+
}),
|
|
36033
|
+
skipped.length > 0 ? /* @__PURE__ */ jsxs(Box, { border: true, className: "rounded-md border-border bg-card p-3", children: [
|
|
36034
|
+
/* @__PURE__ */ jsxs(Box, { className: "flex items-center gap-2 pb-2", children: [
|
|
36035
|
+
/* @__PURE__ */ jsx(Typography, { variant: "label", children: "Skipped" }),
|
|
36036
|
+
/* @__PURE__ */ jsx(Badge, { amount: skipped.length })
|
|
36037
|
+
] }),
|
|
36038
|
+
skipped.map((element) => /* @__PURE__ */ jsxs(Box, { className: "flex items-baseline gap-2 py-1", children: [
|
|
36039
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body2", children: element.ref }),
|
|
36040
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: element.reason })
|
|
36041
|
+
] }, element.ref))
|
|
36042
|
+
] }) : null,
|
|
36043
|
+
onConfirm || onCancel ? /* @__PURE__ */ jsxs(Box, { className: "flex items-center justify-end gap-2", children: [
|
|
36044
|
+
onCancel ? /* @__PURE__ */ jsx(Button, { variant: "default", label: cancelLabel, onClick: onCancel }) : null,
|
|
36045
|
+
onConfirm ? /* @__PURE__ */ jsx(Button, { variant: "primary", label: confirmLabel, onClick: onConfirm }) : null
|
|
36046
|
+
] }) : null
|
|
36047
|
+
] });
|
|
36048
|
+
};
|
|
36049
|
+
}
|
|
36050
|
+
});
|
|
36051
|
+
var PIPELINE, DEFAULT_LABELS, ImportProgress;
|
|
36052
|
+
var init_ImportProgress = __esm({
|
|
36053
|
+
"components/core/molecules/import/ImportProgress.tsx"() {
|
|
36054
|
+
"use client";
|
|
36055
|
+
init_Box();
|
|
36056
|
+
init_Badge();
|
|
36057
|
+
init_Icon();
|
|
36058
|
+
init_Typography();
|
|
36059
|
+
init_cn();
|
|
36060
|
+
PIPELINE = [
|
|
36061
|
+
"fetching",
|
|
36062
|
+
"mapping",
|
|
36063
|
+
"reviewing",
|
|
36064
|
+
"committing"
|
|
36065
|
+
];
|
|
36066
|
+
DEFAULT_LABELS = {
|
|
36067
|
+
fetching: "Fetching",
|
|
36068
|
+
mapping: "Mapping",
|
|
36069
|
+
reviewing: "Reviewing",
|
|
36070
|
+
committing: "Committing",
|
|
36071
|
+
done: "Done",
|
|
36072
|
+
failed: "Failed"
|
|
36073
|
+
};
|
|
36074
|
+
ImportProgress = ({
|
|
36075
|
+
step,
|
|
36076
|
+
counts,
|
|
36077
|
+
labels,
|
|
36078
|
+
className
|
|
36079
|
+
}) => {
|
|
36080
|
+
const label = (key) => labels?.[key] ?? DEFAULT_LABELS[key];
|
|
36081
|
+
const currentIndex = step === "done" || step === "failed" ? PIPELINE.length : PIPELINE.indexOf(step);
|
|
36082
|
+
return /* @__PURE__ */ jsxs(Box, { className: cn("flex flex-col gap-3", className), children: [
|
|
36083
|
+
/* @__PURE__ */ jsxs(Box, { className: "flex items-center gap-2", children: [
|
|
36084
|
+
PIPELINE.map((key, index) => {
|
|
36085
|
+
const isComplete = index < currentIndex;
|
|
36086
|
+
const isActive = index === currentIndex;
|
|
36087
|
+
return /* @__PURE__ */ jsxs(React82__default.Fragment, { children: [
|
|
36088
|
+
index > 0 ? /* @__PURE__ */ jsx(Box, { className: "h-px w-4 bg-border" }) : null,
|
|
36089
|
+
/* @__PURE__ */ jsxs(Box, { className: "flex items-center gap-1", "data-testid": `import-progress-step-${key}`, children: [
|
|
36090
|
+
/* @__PURE__ */ jsx(
|
|
36091
|
+
Icon,
|
|
36092
|
+
{
|
|
36093
|
+
icon: isComplete ? "check" : isActive ? "loader" : "circle",
|
|
36094
|
+
size: "sm",
|
|
36095
|
+
className: cn(
|
|
36096
|
+
isComplete ? "text-success" : isActive ? "text-primary" : "text-muted-foreground"
|
|
36097
|
+
)
|
|
36098
|
+
}
|
|
36099
|
+
),
|
|
36100
|
+
/* @__PURE__ */ jsx(
|
|
36101
|
+
Typography,
|
|
36102
|
+
{
|
|
36103
|
+
variant: "caption",
|
|
36104
|
+
className: cn(isActive ? "text-foreground" : "text-muted-foreground"),
|
|
36105
|
+
children: label(key)
|
|
36106
|
+
}
|
|
36107
|
+
)
|
|
36108
|
+
] })
|
|
36109
|
+
] }, key);
|
|
36110
|
+
}),
|
|
36111
|
+
step === "done" || step === "failed" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
36112
|
+
/* @__PURE__ */ jsx(Box, { className: "h-px w-4 bg-border" }),
|
|
36113
|
+
/* @__PURE__ */ jsxs(Box, { className: "flex items-center gap-1", "data-testid": `import-progress-step-${step}`, children: [
|
|
36114
|
+
/* @__PURE__ */ jsx(
|
|
36115
|
+
Icon,
|
|
36116
|
+
{
|
|
36117
|
+
icon: step === "done" ? "check" : "x",
|
|
36118
|
+
size: "sm",
|
|
36119
|
+
className: step === "done" ? "text-success" : "text-error"
|
|
36120
|
+
}
|
|
36121
|
+
),
|
|
36122
|
+
/* @__PURE__ */ jsx(
|
|
36123
|
+
Typography,
|
|
36124
|
+
{
|
|
36125
|
+
variant: "caption",
|
|
36126
|
+
className: step === "done" ? "text-success" : "text-error",
|
|
36127
|
+
children: label(step)
|
|
36128
|
+
}
|
|
36129
|
+
)
|
|
36130
|
+
] })
|
|
36131
|
+
] }) : null
|
|
36132
|
+
] }),
|
|
36133
|
+
counts ? /* @__PURE__ */ jsxs(Box, { className: "flex items-center gap-2", children: [
|
|
36134
|
+
counts.staged !== void 0 ? /* @__PURE__ */ jsx(Badge, { label: `Staged ${counts.staged}` }) : null,
|
|
36135
|
+
counts.committed !== void 0 ? /* @__PURE__ */ jsx(Badge, { variant: "success", label: `Committed ${counts.committed}` }) : null,
|
|
36136
|
+
counts.failed !== void 0 ? /* @__PURE__ */ jsx(Badge, { variant: "danger", label: `Failed ${counts.failed}` }) : null
|
|
36137
|
+
] }) : null
|
|
36138
|
+
] });
|
|
36139
|
+
};
|
|
36140
|
+
}
|
|
36141
|
+
});
|
|
36142
|
+
|
|
36143
|
+
// components/core/molecules/import/index.ts
|
|
36144
|
+
var init_import = __esm({
|
|
36145
|
+
"components/core/molecules/import/index.ts"() {
|
|
36146
|
+
}
|
|
36147
|
+
});
|
|
35864
36148
|
var ReflectionBlock;
|
|
35865
36149
|
var init_ReflectionBlock = __esm({
|
|
35866
36150
|
"components/core/molecules/ReflectionBlock.tsx"() {
|
|
@@ -35935,6 +36219,7 @@ var init_molecules2 = __esm({
|
|
|
35935
36219
|
init_EmptyState();
|
|
35936
36220
|
init_Pagination();
|
|
35937
36221
|
init_molecules();
|
|
36222
|
+
init_import();
|
|
35938
36223
|
}
|
|
35939
36224
|
});
|
|
35940
36225
|
|
|
@@ -36415,7 +36700,7 @@ function getBadgeVariant(fieldName, value) {
|
|
|
36415
36700
|
function formatFieldLabel(fieldName) {
|
|
36416
36701
|
return fieldName.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase());
|
|
36417
36702
|
}
|
|
36418
|
-
function
|
|
36703
|
+
function formatFieldValue2(value, fieldName) {
|
|
36419
36704
|
if (typeof value === "number") {
|
|
36420
36705
|
if (fieldName.toLowerCase().includes("progress") || fieldName.toLowerCase().includes("percent")) {
|
|
36421
36706
|
return `${value}%`;
|
|
@@ -36510,7 +36795,7 @@ function renderRichFieldValue(value, fieldName, fieldType) {
|
|
|
36510
36795
|
return str;
|
|
36511
36796
|
}
|
|
36512
36797
|
default:
|
|
36513
|
-
return
|
|
36798
|
+
return formatFieldValue2(value, fieldName);
|
|
36514
36799
|
}
|
|
36515
36800
|
}
|
|
36516
36801
|
function normalizeFieldDefs(fields) {
|
|
@@ -36621,7 +36906,7 @@ var init_DetailPanel = __esm({
|
|
|
36621
36906
|
const value = getNestedValue(normalizedData, field);
|
|
36622
36907
|
return {
|
|
36623
36908
|
label: labelFor(field),
|
|
36624
|
-
value:
|
|
36909
|
+
value: formatFieldValue2(value, field),
|
|
36625
36910
|
icon: getFieldIcon(field)
|
|
36626
36911
|
};
|
|
36627
36912
|
}
|
|
@@ -42652,6 +42937,9 @@ var init_component_registry_generated = __esm({
|
|
|
42652
42937
|
init_HeroOrganism();
|
|
42653
42938
|
init_HeroSection();
|
|
42654
42939
|
init_Icon();
|
|
42940
|
+
init_ImportPreviewTree();
|
|
42941
|
+
init_ImportProgress();
|
|
42942
|
+
init_ImportSourcePicker();
|
|
42655
42943
|
init_InfiniteScrollSentinel();
|
|
42656
42944
|
init_Input();
|
|
42657
42945
|
init_InputGroup();
|
|
@@ -42915,6 +43203,9 @@ var init_component_registry_generated = __esm({
|
|
|
42915
43203
|
"HeroOrganism": HeroOrganism,
|
|
42916
43204
|
"HeroSection": HeroSection,
|
|
42917
43205
|
"Icon": Icon,
|
|
43206
|
+
"ImportPreviewTree": ImportPreviewTree,
|
|
43207
|
+
"ImportProgress": ImportProgress,
|
|
43208
|
+
"ImportSourcePicker": ImportSourcePicker,
|
|
42918
43209
|
"InfiniteScrollSentinel": InfiniteScrollSentinel,
|
|
42919
43210
|
"Input": Input,
|
|
42920
43211
|
"InputGroup": InputGroup,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@almadar/ui",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.129.0",
|
|
4
4
|
"description": "React UI components, hooks, and providers for Almadar",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -110,6 +110,7 @@
|
|
|
110
110
|
"index.css",
|
|
111
111
|
"themes",
|
|
112
112
|
"locales",
|
|
113
|
+
"scripts",
|
|
113
114
|
"tailwind-preset.cjs"
|
|
114
115
|
],
|
|
115
116
|
"publishConfig": {
|
|
@@ -117,12 +118,12 @@
|
|
|
117
118
|
"access": "public"
|
|
118
119
|
},
|
|
119
120
|
"dependencies": {
|
|
120
|
-
"@almadar/core": "^10.
|
|
121
|
+
"@almadar/core": "^10.38.0",
|
|
121
122
|
"@almadar/evaluator": "^2.37.0",
|
|
122
123
|
"@almadar/logger": "^1.10.0",
|
|
123
|
-
"@almadar/runtime": "^6.
|
|
124
|
-
"@almadar/std": "^16.
|
|
125
|
-
"@almadar/syntax": "^1.
|
|
124
|
+
"@almadar/runtime": "^6.41.0",
|
|
125
|
+
"@almadar/std": "^16.147.0",
|
|
126
|
+
"@almadar/syntax": "^1.13.0",
|
|
126
127
|
"@dnd-kit/core": "^6.3.1",
|
|
127
128
|
"@dnd-kit/sortable": "^10.0.0",
|
|
128
129
|
"@dnd-kit/utilities": "^3.2.2",
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* Tailwind Safelist Audit
|
|
4
|
+
*
|
|
5
|
+
* Scans all @almadar/ui source files for Tailwind classes that need
|
|
6
|
+
* to be in the safelist (because consuming apps can't extract them
|
|
7
|
+
* from compiled dist/ files). Compares against the current safelist
|
|
8
|
+
* in tailwind-preset.cjs and reports missing entries.
|
|
9
|
+
*
|
|
10
|
+
* Classes that need safelisting:
|
|
11
|
+
* 1. Arbitrary value classes: bg-[var(--color-X)], min-h-[200px], etc.
|
|
12
|
+
* 2. Opacity modifiers on semantic colors: bg-primary/10, text-foreground/60
|
|
13
|
+
* 3. Responsive prefixes on dynamic values: sm:grid-cols-2
|
|
14
|
+
* 4. State prefixes on CSS vars: hover:bg-[var(--color-X)]
|
|
15
|
+
*
|
|
16
|
+
* Standard utility classes (flex, p-4, rounded-lg) are fine because
|
|
17
|
+
* the shell template's tailwind.config.js includes the dist/ path in
|
|
18
|
+
* content scanning. But CSS-variable-based arbitrary classes use bracket
|
|
19
|
+
* notation that Tailwind can't extract from minified JS.
|
|
20
|
+
*
|
|
21
|
+
* Usage:
|
|
22
|
+
* npx tsx scripts/audit-tailwind-safelist.ts
|
|
23
|
+
* npx tsx scripts/audit-tailwind-safelist.ts --fix (appends missing to preset)
|
|
24
|
+
*
|
|
25
|
+
* @packageDocumentation
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { readFileSync, writeFileSync, readdirSync, statSync } from 'fs';
|
|
29
|
+
import { join, relative } from 'path';
|
|
30
|
+
|
|
31
|
+
const ROOT = join(import.meta.dirname, '..');
|
|
32
|
+
const PRESET_PATH = join(ROOT, 'tailwind-preset.cjs');
|
|
33
|
+
const SCAN_DIRS = ['components', 'runtime', 'renderer', 'context', 'providers', 'hooks'];
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Regex patterns for classes that need safelisting
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
// Matches arbitrary value classes: bg-[var(...)], min-h-[200px], w-[300px], etc.
|
|
40
|
+
// Requires a hyphen before the bracket to avoid matching JS array access like arr[0]
|
|
41
|
+
const ARBITRARY_RE = /(?:^|\s|["'`])([a-z][a-z0-9]*(?::[a-z][a-z0-9-]*)+-\[[^\]]+\]|[a-z][a-z0-9]*-[a-z0-9-]*-\[[^\]]+\]|[a-z][a-z0-9]*-\[[^\]]+\])/g;
|
|
42
|
+
|
|
43
|
+
// Matches opacity modifiers on semantic colors: bg-primary/10, text-foreground/60
|
|
44
|
+
const OPACITY_RE = /(?:^|\s|["'`])((?:bg|text|border|ring|from|to|via|hover:bg|hover:text|hover:border|dark:bg|dark:hover:bg|group-hover:bg|group-hover:text|peer-focus:ring|focus:ring)-(?:primary|secondary|muted|accent|foreground|card|surface|background|border|error|success|warning|info|ring|input|muted-foreground|card-foreground|primary-foreground|secondary-foreground|error-foreground|success-foreground|warning-foreground|info-foreground)\/\d+)/g;
|
|
45
|
+
|
|
46
|
+
// Matches classes with [var(--...)] or [length:var(--...)]
|
|
47
|
+
const CSS_VAR_RE = /(?:^|\s|["'`])([a-z][a-z0-9-]*(?::[a-z][a-z0-9-]*)*-\[(?:length:|color:)?var\(--[a-z0-9-]+\)[^\]]*\])/g;
|
|
48
|
+
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// File scanner
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
function getAllFiles(dir: string, ext = '.tsx'): string[] {
|
|
54
|
+
const results: string[] = [];
|
|
55
|
+
try {
|
|
56
|
+
for (const entry of readdirSync(dir)) {
|
|
57
|
+
const full = join(dir, entry);
|
|
58
|
+
const stat = statSync(full);
|
|
59
|
+
if (stat.isDirectory() && entry !== 'node_modules' && entry !== 'dist' && entry !== '.storybook') {
|
|
60
|
+
results.push(...getAllFiles(full, ext));
|
|
61
|
+
} else if (entry.endsWith(ext) || entry.endsWith('.ts')) {
|
|
62
|
+
results.push(full);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
// directory doesn't exist
|
|
67
|
+
}
|
|
68
|
+
return results;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function extractSafelistCandidates(content: string): Set<string> {
|
|
72
|
+
const candidates = new Set<string>();
|
|
73
|
+
|
|
74
|
+
// Find arbitrary value classes
|
|
75
|
+
for (const match of content.matchAll(ARBITRARY_RE)) {
|
|
76
|
+
const cls = match[1];
|
|
77
|
+
if (cls && !cls.startsWith('//') && !cls.startsWith('*')) {
|
|
78
|
+
candidates.add(cls);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Find opacity modifier classes
|
|
83
|
+
for (const match of content.matchAll(OPACITY_RE)) {
|
|
84
|
+
if (match[1]) candidates.add(match[1]);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Find CSS variable classes
|
|
88
|
+
for (const match of content.matchAll(CSS_VAR_RE)) {
|
|
89
|
+
if (match[1]) candidates.add(match[1]);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return candidates;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// Safelist parser
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
function getCurrentSafelist(): Set<string> {
|
|
100
|
+
const content = readFileSync(PRESET_PATH, 'utf-8');
|
|
101
|
+
const safelist = new Set<string>();
|
|
102
|
+
|
|
103
|
+
// Find the safelist array - match from 'safelist: [' to the matching ']'
|
|
104
|
+
const startIdx = content.indexOf('safelist:');
|
|
105
|
+
if (startIdx === -1) return safelist;
|
|
106
|
+
|
|
107
|
+
const bracketStart = content.indexOf('[', startIdx);
|
|
108
|
+
if (bracketStart === -1) return safelist;
|
|
109
|
+
|
|
110
|
+
// Find the matching closing bracket (handle nested brackets)
|
|
111
|
+
let depth = 0;
|
|
112
|
+
let bracketEnd = -1;
|
|
113
|
+
for (let i = bracketStart; i < content.length; i++) {
|
|
114
|
+
if (content[i] === '[') depth++;
|
|
115
|
+
if (content[i] === ']') {
|
|
116
|
+
depth--;
|
|
117
|
+
if (depth === 0) { bracketEnd = i; break; }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (bracketEnd === -1) return safelist;
|
|
121
|
+
|
|
122
|
+
const safelistContent = content.slice(bracketStart + 1, bracketEnd);
|
|
123
|
+
const entries = safelistContent.matchAll(/['"]([^'"]+)['"]/g);
|
|
124
|
+
for (const m of entries) {
|
|
125
|
+
if (m[1]) safelist.add(m[1]);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return safelist;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// Main
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
const fix = process.argv.includes('--fix');
|
|
136
|
+
|
|
137
|
+
console.log('Tailwind Safelist Audit');
|
|
138
|
+
console.log('='.repeat(60));
|
|
139
|
+
console.log(`Scanning: ${SCAN_DIRS.join(', ')}`);
|
|
140
|
+
console.log(`Preset: ${relative(ROOT, PRESET_PATH)}`);
|
|
141
|
+
console.log();
|
|
142
|
+
|
|
143
|
+
// Scan all source files
|
|
144
|
+
const allCandidates = new Map<string, Set<string>>(); // class -> set of files
|
|
145
|
+
let fileCount = 0;
|
|
146
|
+
|
|
147
|
+
for (const dir of SCAN_DIRS) {
|
|
148
|
+
const files = getAllFiles(join(ROOT, dir));
|
|
149
|
+
for (const file of files) {
|
|
150
|
+
if (file.includes('.stories.') || file.includes('.test.') || file.includes('.spec.')) continue;
|
|
151
|
+
fileCount++;
|
|
152
|
+
const content = readFileSync(file, 'utf-8');
|
|
153
|
+
const candidates = extractSafelistCandidates(content);
|
|
154
|
+
for (const cls of candidates) {
|
|
155
|
+
if (!allCandidates.has(cls)) allCandidates.set(cls, new Set());
|
|
156
|
+
allCandidates.get(cls)!.add(relative(ROOT, file));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
console.log(`Scanned ${fileCount} files, found ${allCandidates.size} safelist candidates`);
|
|
162
|
+
console.log();
|
|
163
|
+
|
|
164
|
+
// Compare against current safelist
|
|
165
|
+
const currentSafelist = getCurrentSafelist();
|
|
166
|
+
const missing: Array<{ cls: string; files: string[] }> = [];
|
|
167
|
+
const alreadySafe: string[] = [];
|
|
168
|
+
|
|
169
|
+
for (const [cls, files] of allCandidates) {
|
|
170
|
+
if (currentSafelist.has(cls)) {
|
|
171
|
+
alreadySafe.push(cls);
|
|
172
|
+
} else {
|
|
173
|
+
missing.push({ cls, files: Array.from(files) });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Also exclude standard utility classes that Tailwind extracts from dist/ scanning.
|
|
178
|
+
// Only arbitrary values ([...]), opacity modifiers (/N), and CSS var classes need safelisting.
|
|
179
|
+
const needsSafelist = (cls: string) =>
|
|
180
|
+
cls.includes('[') || cls.includes('/') || cls.includes('var(');
|
|
181
|
+
|
|
182
|
+
const filtered = missing.filter(m => needsSafelist(m.cls));
|
|
183
|
+
const skipped = missing.filter(m => !needsSafelist(m.cls));
|
|
184
|
+
missing.length = 0;
|
|
185
|
+
missing.push(...filtered);
|
|
186
|
+
|
|
187
|
+
// Report
|
|
188
|
+
console.log(`Current safelist: ${currentSafelist.size} entries`);
|
|
189
|
+
console.log(`Already safelisted: ${alreadySafe.length}`);
|
|
190
|
+
console.log(`Standard utilities (no safelist needed): ${skipped.length}`);
|
|
191
|
+
console.log(`Missing from safelist: ${missing.length}`);
|
|
192
|
+
console.log();
|
|
193
|
+
|
|
194
|
+
if (missing.length === 0) {
|
|
195
|
+
console.log('All safelist candidates are covered. No changes needed.');
|
|
196
|
+
process.exit(0);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Sort missing by class name
|
|
200
|
+
missing.sort((a, b) => a.cls.localeCompare(b.cls));
|
|
201
|
+
|
|
202
|
+
console.log('Missing classes:');
|
|
203
|
+
for (const { cls, files } of missing) {
|
|
204
|
+
console.log(` ${cls}`);
|
|
205
|
+
for (const f of files.slice(0, 3)) {
|
|
206
|
+
console.log(` <- ${f}`);
|
|
207
|
+
}
|
|
208
|
+
if (files.length > 3) {
|
|
209
|
+
console.log(` ... and ${files.length - 3} more files`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (fix) {
|
|
214
|
+
console.log();
|
|
215
|
+
console.log('Appending missing classes to safelist...');
|
|
216
|
+
|
|
217
|
+
const presetContent = readFileSync(PRESET_PATH, 'utf-8');
|
|
218
|
+
|
|
219
|
+
// Find the end of the safelist array and insert before the closing ]
|
|
220
|
+
const safelistEndIdx = presetContent.indexOf('],', presetContent.indexOf('safelist:'));
|
|
221
|
+
if (safelistEndIdx === -1) {
|
|
222
|
+
console.error('Could not find safelist closing bracket in preset file');
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const newEntries = missing.map(m => ` '${m.cls}',`).join('\n');
|
|
227
|
+
const comment = ` // Auto-added by audit-tailwind-safelist.ts (${new Date().toISOString().split('T')[0]})`;
|
|
228
|
+
const updated = presetContent.slice(0, safelistEndIdx) +
|
|
229
|
+
'\n' + comment + '\n' + newEntries + '\n' +
|
|
230
|
+
presetContent.slice(safelistEndIdx);
|
|
231
|
+
|
|
232
|
+
writeFileSync(PRESET_PATH, updated, 'utf-8');
|
|
233
|
+
console.log(`Added ${missing.length} classes to safelist.`);
|
|
234
|
+
} else {
|
|
235
|
+
console.log();
|
|
236
|
+
console.log('Run with --fix to add missing classes to the preset.');
|
|
237
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Export Static Design System
|
|
3
|
+
*
|
|
4
|
+
* After Storybook build, this script packages the design system
|
|
5
|
+
* into a client-ready deliverable (PDF or HTML bundle).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
const STORYBOOK_DIR = path.join(__dirname, '..', 'storybook-static');
|
|
12
|
+
const OUTPUT_DIR = path.join(__dirname, '..', 'exports');
|
|
13
|
+
|
|
14
|
+
async function exportDesignSystem() {
|
|
15
|
+
console.log('📦 Exporting design system...');
|
|
16
|
+
|
|
17
|
+
// Ensure output directory exists
|
|
18
|
+
if (!fs.existsSync(OUTPUT_DIR)) {
|
|
19
|
+
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Check if storybook build exists
|
|
23
|
+
if (!fs.existsSync(STORYBOOK_DIR)) {
|
|
24
|
+
console.error('❌ Storybook build not found. Run `npm run build-storybook` first.');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Copy storybook-static to exports with timestamp
|
|
29
|
+
const timestamp = new Date().toISOString().split('T')[0];
|
|
30
|
+
const exportName = `design-system-${timestamp}`;
|
|
31
|
+
const exportPath = path.join(OUTPUT_DIR, exportName);
|
|
32
|
+
|
|
33
|
+
// Copy directory
|
|
34
|
+
fs.cpSync(STORYBOOK_DIR, exportPath, { recursive: true });
|
|
35
|
+
|
|
36
|
+
console.log(`✅ Design system exported to: ${exportPath}`);
|
|
37
|
+
console.log(` Open ${exportPath}/index.html in a browser to view.`);
|
|
38
|
+
|
|
39
|
+
// Create a manifest
|
|
40
|
+
const manifest = {
|
|
41
|
+
name: '@orbital/design-system',
|
|
42
|
+
version: '1.0.0',
|
|
43
|
+
exportedAt: new Date().toISOString(),
|
|
44
|
+
themes: ['wireframe', 'minimalist', 'almadar', 'winning-11'],
|
|
45
|
+
components: {
|
|
46
|
+
atoms: ['Avatar', 'Badge', 'Button', 'Card', 'Checkbox', 'Divider', 'Input', 'Radio', 'Select', 'Spinner', 'Typography'],
|
|
47
|
+
molecules: ['Accordion', 'Alert', 'Modal', 'Tabs', 'Toast'],
|
|
48
|
+
organisms: ['ConfirmDialog', 'DataTable', 'Header', 'Sidebar', 'StatCard', 'WizardContainer'],
|
|
49
|
+
templates: ['AuthLayout', 'CounterTemplate', 'DashboardLayout', 'GameTemplate', 'GenericAppTemplate'],
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
fs.writeFileSync(
|
|
54
|
+
path.join(exportPath, 'manifest.json'),
|
|
55
|
+
JSON.stringify(manifest, null, 2)
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
console.log(` Manifest created at: ${exportPath}/manifest.json`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
exportDesignSystem().catch(console.error);
|