@assure-one/design-system 1.43.1 → 1.44.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.
@@ -1,7 +1,7 @@
1
1
  "use client";
2
- export { AIReceiptPanel, AiDraftCard, Assignee, AttentionItem, Blockquote, BottomNav, CategoryDivider, CategoryTag, ChannelTabs, ClientRailGroupHeader, ClientRailItem, Dash, DashGrid, DocumentChecklist, DocumentFileCard, DocumentFileLine, DocumentFileRow, DocumentRequestCard, DocumentRequestDetail, DocumentRequestField, DocumentRequestUpload, DocumentSourceFilter, DocumentSourceTag, DocumentsWorkspaceLayout, EmailMessageCard, EngagementCard, EngagementTimeline, EngagementTimelineStep, IntentBadge, MissingDocumentsPanel, MoneyCell, NewMenu, Section, SectionHead, SectionHeader, SignatureEditor, StagePill, Stat, StatusPill, SuggestionPills, TagsCell, TeamMemberSelect, TimeLogger, TimeLoggerActions, TimeLoggerBillable, TimeLoggerContextRow, TimeLoggerEntry, TimeLoggerEntryList, TimeLoggerField, TimeLoggerFooter, TimeLoggerHeader, TimeLoggerNotes, TimeLoggerTimer, formatClock, formatCurrency, formatDuration, parseDuration, useStopwatch } from '../chunk-5AL4W32Z.js';
3
- import '../chunk-VK6SW34V.js';
4
- import '../chunk-TBDTXAFH.js';
2
+ export { AIReceiptPanel, AiDraftCard, Assignee, AttentionItem, Blockquote, BottomNav, CategoryDivider, CategoryTag, ChannelTabs, ClientRailGroupHeader, ClientRailItem, Dash, DashGrid, DocumentChecklist, DocumentFileCard, DocumentFileLine, DocumentFileRow, DocumentRequestCard, DocumentRequestDetail, DocumentRequestField, DocumentRequestUpload, DocumentSourceFilter, DocumentSourceTag, DocumentsWorkspaceLayout, EmailMessageCard, EngagementCard, EngagementTimeline, EngagementTimelineStep, IntentBadge, MissingDocumentsPanel, MoneyCell, NewMenu, Section, SectionHead, SectionHeader, SignatureEditor, StagePill, Stat, StatusPill, SuggestionPills, TagsCell, TeamMemberSelect, TimeLogger, TimeLoggerActions, TimeLoggerBillable, TimeLoggerContextRow, TimeLoggerEntry, TimeLoggerEntryList, TimeLoggerField, TimeLoggerFooter, TimeLoggerHeader, TimeLoggerNotes, TimeLoggerTimer, formatClock, formatCurrency, formatDuration, parseDuration, useStopwatch } from '../chunk-PC6M4QZ2.js';
3
+ import '../chunk-Z7QPHPNV.js';
4
+ import '../chunk-E2URQD5A.js';
5
5
  import '../chunk-YRHRVOLV.js';
6
6
  //# sourceMappingURL=index.js.map
7
7
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  "use client";
2
- export { AgreementPaneHeading, AgreementViewer, AssureAuditBrandIcon, AssureBooksBrandIcon, AssureProBrandIcon, AssureTaxBrandIcon, BRAND_PRODUCTS, ClientSelect, ComingSoon, Grid, Logo, ProposalAddOn, ProposalBillingTerms, ProposalConsentGate, ProposalCustomPage, ProposalNote, ProposalPackageCard, ProposalPaymentCapture, ProposalPricingSummary, ProposalServiceRow, ProposalSignatureBlock, ProposalSignerList, QUESTION_TYPES, QUESTION_TYPE_CONTRACT_VERSION, Questions, SidebarBrandSwitcher, SidebarBrandSwitcherTile, brandLabel } from '../chunk-L5IQK3GV.js';
3
- export { DocumentDetailActions, DocumentDetailBody, DocumentDetailHeader, DocumentDetailMetaRow, DocumentDetailPanel, DocumentDetailRequester, DocumentDetailTitle, DocumentList, DocumentListSection, DocumentRow } from '../chunk-VK6SW34V.js';
4
- import '../chunk-TBDTXAFH.js';
2
+ export { AgreementPaneHeading, AgreementViewer, AssureAuditBrandIcon, AssureBooksBrandIcon, AssureProBrandIcon, AssureTaxBrandIcon, BRAND_PRODUCTS, ClientSelect, ComingSoon, Grid, Logo, ProposalAddOn, ProposalBillingTerms, ProposalConsentGate, ProposalCustomPage, ProposalNote, ProposalPackageCard, ProposalPaymentCapture, ProposalPricingSummary, ProposalServiceRow, ProposalSignatureBlock, ProposalSignerList, QUESTION_TYPES, QUESTION_TYPE_CONTRACT_VERSION, Questions, SidebarBrandSwitcher, SidebarBrandSwitcherTile, brandLabel } from '../chunk-4TXBY6P7.js';
3
+ export { DocumentDetailActions, DocumentDetailBody, DocumentDetailHeader, DocumentDetailMetaRow, DocumentDetailPanel, DocumentDetailRequester, DocumentDetailTitle, DocumentList, DocumentListSection, DocumentRow } from '../chunk-Z7QPHPNV.js';
4
+ import '../chunk-E2URQD5A.js';
5
5
  import '../chunk-EUHGZA6Q.js';
6
6
  import '../chunk-YRHRVOLV.js';
7
7
  //# sourceMappingURL=index.js.map
@@ -2,7 +2,7 @@
2
2
 
3
3
  <!-- Generated by `pnpm docs:catalogue` from api/*.api.md and docs/components.registry.json. Do not edit by hand; edit the registry and regenerate. -->
4
4
 
5
- Every public component of `@assure-one/design-system`, in one place: **496 component exports** — 183 component families with 228 compound parts, and 168 icons — plus the hooks, utilities, tokens and testing helpers the package exports. The list is derived from the API reports, so a public component cannot be missing from this page (`pnpm docs:catalogue --check` runs in `pnpm test:contracts`).
5
+ Every public component of `@assure-one/design-system`, in one place: **522 component exports** — 183 component families with 228 compound parts, and 194 icons — plus the hooks, utilities, tokens and testing helpers the package exports. The list is derived from the API reports, so a public component cannot be missing from this page (`pnpm docs:catalogue --check` runs in `pnpm test:contracts`).
6
6
 
7
7
  **How to use this page.** Start from the ["Pick a component"](#pick-a-component) table with the UI need you have, or from ["Never hand-roll"](#never-hand-roll) with the raw element you were about to write; links land on the category section that holds the row. Every row links to the story that shows the states. The short rulebook for agents and new contributors is [`docs/for-ai-agents.md`](./for-ai-agents.md).
8
8
 
@@ -346,14 +346,14 @@ Every glyph comes from the catalogue; never inline an `<svg>` or add `lucide-rea
346
346
 
347
347
  ### Icons (experimental)
348
348
 
349
- The UI glyph catalogue: Lucide glyphs on the 24-unit grid, `currentColor` strokes at 2, `aria-hidden` unless titled. Import from `@assure-one/design-system/icons` (server-safe) or the root. 166 exports, plus `createIcon`.
349
+ The UI glyph catalogue and the only UI icon source: Lucide glyphs on the 24-unit grid, `currentColor` strokes at 2, `aria-hidden` unless titled. Import from `@assure-one/design-system/icons` (server-safe) or the root. `iconToSvg` / `iconCursor` give the same Lucide glyph as an SVG string or a CSS cursor where React cannot render. 192 exports, plus `createIcon`, `iconCursor`, `iconToSvg`.
350
350
 
351
- **Use when:** Any glyph in a control, a list row, a status, an empty state; `createIcon` to register a one-off glyph in the same contract.
351
+ **Use when:** Any glyph in a control, a list row, a status, an empty state; `iconToSvg` in a non-React shell, `iconCursor` for a tool-mode cursor. Products never draw their own icons (`createIcon` is the catalogue's builder).
352
352
  **Don't use when:** You need a product mark (brand icon family or `Logo`), a state-driven glyph (`StatusIcon`, `PriorityIcon`) or an illustration.
353
- **Replaces:** `<svg>`, `lucide-react`
353
+ **Replaces:** `<svg>`, `lucide-react`, `icon characters (✓ ✕ → ↗)`, `SVG data-URL icons`
354
354
  **Story:** [src/primitives/icons.stories.tsx](https://github.com/Assure-One/design-system/blob/main/src/primitives/icons.stories.tsx)
355
355
 
356
- `AlertCircleIcon` · `AlertTriangleIcon` · `AlertTriangleSolidIcon` · `ArchiveIcon` · `ArrowDownIcon` · `ArrowLeftIcon` · `ArrowRightIcon` · `ArrowUpIcon` · `ArrowUpRightIcon` · `AtSignIcon` · `BanIcon` · `BarChartIcon` · `BellIcon` · `BoldIcon` · `BrainIcon` · `BriefcaseIcon` · `Building2Icon` · `BuildingIcon` · `CalculatorIcon` · `CalendarIcon` · `CameraIcon` · `ChatBubbleIcon` · `CheckCheckIcon` · `CheckCircle2Icon` · `CheckCircleSolidIcon` · `CheckIcon` · `ChevronDownIcon` · `ChevronLeftIcon` · `ChevronRightIcon` · `ChevronsDownUpIcon` · `ChevronsLeftIcon` · `ChevronsRightIcon` · `ChevronsUpDownIcon` · `ChevronUpIcon` · `CircleDotIcon` · `CircleIcon` · `ClipboardCheckIcon` · `ClockIcon` · `CommandIcon` · `CopyIcon` · `CornerDownLeftIcon` · `CreditCardIcon` · `DocumentIcon` · `DollarSignIcon` · `DownloadIcon` · `ExternalLinkIcon` · `ExtractIcon` · `EyeIcon` · `EyeOffIcon` · `FileChartColumnIcon` · `FileIcon` · `FileReturnIcon` · `FileTextIcon` · `FilterIcon` · `FlagIcon` · `FolderClosedIcon` · `FolderDownIcon` · `FolderOpenIcon` · `FolderPlusIcon` · `FolderUpIcon` · `GlobeIcon` · `GraduationCapIcon` · `GripHorizontalIcon` · `GripVerticalIcon` · `HelpCircleIcon` · `HistoryIcon` · `HomeIcon` · `InboxIcon` · `InfoCircleSolidIcon` · `InfoIcon` · `ItalicIcon` · `KanbanIcon` · `KeyIcon` · `LandmarkIcon` · `LayersIcon` · `LayoutDashboardIcon` · `LayoutGridIcon` · `LayoutListIcon` · `LightningIcon` · `Link2Icon` · `LinkIcon` · `ListChecksIcon` · `ListIcon` · `ListOrderedIcon` · `LoaderIcon` · `LockIcon` · `LockKeyholeIcon` · `LockOpenIcon` · `LogOutIcon` · `MailIcon` · `MapPinIcon` · `MaximizeIcon` · `MenuIcon` · `MessageCircleIcon` · `MessageCircleMoreIcon` · `MessageCircleWarningIcon` · `MicIcon` · `MinimizeIcon` · `MinusIcon` · `MonitorIcon` · `MoonIcon` · `MoreHorizontalIcon` · `MoreVerticalIcon` · `MousePointer2Icon` · `MoveIcon` · `PaletteIcon` · `PanelLeftCloseIcon` · `PanelLeftIcon` · `PanelRightCloseIcon` · `PanelRightIcon` · `PaperclipIcon` · `PauseIcon` · `PencilIcon` · `PenSignIcon` · `PenToolIcon` · `PhoneIcon` · `PlayIcon` · `PlusIcon` · `ReceiptIcon` · `RefreshCwIcon` · `ReplyIcon` · `RotateCcwIcon` · `Rows4Icon` · `RowsIcon` · `SaveIcon` · `SearchIcon` · `SendIcon` · `SettingsIcon` · `ShieldIcon` · `SigmaIcon` · `SlashIcon` · `SmartphoneIcon` · `SparkleIcon` · `SparklesIcon` · `SquarePlayIcon` · `StarIcon` · `StopIcon` · `StrikethroughIcon` · `SunIcon` · `TableIcon` · `TagIcon` · `TeamIcon` · `ThumbsDownIcon` · `ThumbsUpIcon` · `TimerIcon` · `Trash2Icon` · `TrashIcon` · `TrashSolidIcon` · `TrendingDownIcon` · `TrendingUpIcon` · `TypeIcon` · `UnderlineIcon` · `UploadIcon` · `UserCircleIcon` · `UserIcon` · `UsersIcon` · `VideoIcon` · `Volume2Icon` · `VolumeXIcon` · `WorkflowIcon` · `XCircleSolidIcon` · `XIcon` · `ZoomInIcon` · `ZoomOutIcon`
356
+ `AlertCircleIcon` · `AlertTriangleIcon` · `AlertTriangleSolidIcon` · `AlignCenterIcon` · `AlignLeftIcon` · `AlignRightIcon` · `AppWindowIcon` · `ArchiveIcon` · `ArrowDownIcon` · `ArrowLeftIcon` · `ArrowRightIcon` · `ArrowUpIcon` · `ArrowUpRightIcon` · `AtSignIcon` · `BanIcon` · `BarChartIcon` · `BellIcon` · `BellRingIcon` · `BoldIcon` · `BookTextIcon` · `BrainIcon` · `BriefcaseIcon` · `Building2Icon` · `BuildingIcon` · `CalculatorIcon` · `CalendarIcon` · `CameraIcon` · `ChartNoAxesColumnIncreasingIcon` · `ChatBubbleIcon` · `CheckCheckIcon` · `CheckCircle2Icon` · `CheckCircleSolidIcon` · `CheckIcon` · `ChevronDownIcon` · `ChevronLeftIcon` · `ChevronRightIcon` · `ChevronsDownUpIcon` · `ChevronsLeftIcon` · `ChevronsRightIcon` · `ChevronsUpDownIcon` · `ChevronUpIcon` · `CircleDashedIcon` · `CircleDotIcon` · `CircleIcon` · `ClipboardCheckIcon` · `ClockAlertIcon` · `ClockIcon` · `CommandIcon` · `CopyIcon` · `CornerDownLeftIcon` · `CreditCardIcon` · `DecimalsArrowLeftIcon` · `DecimalsArrowRightIcon` · `DocumentIcon` · `DollarSignIcon` · `DownloadIcon` · `ExternalLinkIcon` · `ExtractIcon` · `EyeIcon` · `EyeOffIcon` · `FileChartColumnIcon` · `FileIcon` · `FilePlusIcon` · `FileReturnIcon` · `FileTextIcon` · `FilterIcon` · `FlagIcon` · `FolderClosedIcon` · `FolderDownIcon` · `FolderOpenIcon` · `FolderPlusIcon` · `FolderUpIcon` · `GlobeIcon` · `GraduationCapIcon` · `Grid2x2Icon` · `GripHorizontalIcon` · `GripVerticalIcon` · `HelpCircleIcon` · `HighlighterIcon` · `HistoryIcon` · `HomeIcon` · `InboxIcon` · `InfoCircleSolidIcon` · `InfoIcon` · `ItalicIcon` · `KanbanIcon` · `KeyIcon` · `LandmarkIcon` · `LayersIcon` · `LayoutDashboardIcon` · `LayoutGridIcon` · `LayoutListIcon` · `LightningIcon` · `Link2Icon` · `LinkIcon` · `ListChecksIcon` · `ListIcon` · `ListOrderedIcon` · `LoaderIcon` · `LockIcon` · `LockKeyholeIcon` · `LockOpenIcon` · `LogOutIcon` · `MailIcon` · `MapPinIcon` · `MaximizeIcon` · `MenuIcon` · `MessageCircleIcon` · `MessageCircleMoreIcon` · `MessageCircleWarningIcon` · `MicIcon` · `MinimizeIcon` · `MinusIcon` · `MonitorIcon` · `MoonIcon` · `MoreHorizontalIcon` · `MoreVerticalIcon` · `MousePointer2Icon` · `MoveIcon` · `PaletteIcon` · `PanelLeftCloseIcon` · `PanelLeftIcon` · `PanelRightCloseIcon` · `PanelRightIcon` · `PaperclipIcon` · `PauseIcon` · `PencilIcon` · `PenSignIcon` · `PenToolIcon` · `PercentIcon` · `PhoneIcon` · `PinIcon` · `PinOffIcon` · `PlayIcon` · `PlusIcon` · `ReceiptIcon` · `RefreshCwIcon` · `RepeatIcon` · `ReplyIcon` · `RotateCcwIcon` · `Rows4Icon` · `RowsIcon` · `SaveIcon` · `SearchCheckIcon` · `SearchIcon` · `SendIcon` · `SettingsIcon` · `ShieldIcon` · `SigmaIcon` · `SlashIcon` · `SmartphoneIcon` · `SparkleIcon` · `SparklesIcon` · `SquareExclamationPointIcon` · `SquareFunctionIcon` · `SquareIcon` · `SquarePlayIcon` · `SquareSquareIcon` · `StampIcon` · `StarIcon` · `StopIcon` · `StrikethroughIcon` · `SunIcon` · `TableIcon` · `TagIcon` · `TeamIcon` · `ThumbsDownIcon` · `ThumbsUpIcon` · `TimerIcon` · `Trash2Icon` · `TrashIcon` · `TrashSolidIcon` · `TrendingDownIcon` · `TrendingUpIcon` · `TypeIcon` · `UnderlineIcon` · `Undo2Icon` · `UploadIcon` · `UserCircleIcon` · `UserIcon` · `UsersIcon` · `VideoIcon` · `Volume2Icon` · `VolumeXIcon` · `WorkflowIcon` · `XCircleIcon` · `XCircleSolidIcon` · `XIcon` · `ZoomInIcon` · `ZoomOutIcon`
357
357
 
358
358
  Deprecated aliases (same drawing under an older name): `ArrowRightSmallIcon`, `CloseIcon`.
359
359
 
@@ -532,6 +532,7 @@ The raw element or pattern on the left already has a design-system component. Wr
532
532
  | `hand-rolled toast portal` | [ToastProvider](#feedback) |
533
533
  | `hand-rolled toolbar + <table> + pager around per-table useState` | [DataTable (compound)](#data-display) |
534
534
  | `hard-coded top-[56px] sticky offsets` | [StickyStack](#layout) |
535
+ | `icon characters (✓ ✕ → ↗)` | [Icons](#icons) |
535
536
  | `lucide-react` | [Icons](#icons) |
536
537
  | `navigator.clipboard.writeText + useState` | [CopyButton](#actions) |
537
538
  | `nested div with width:%` | [ProgressBar](#feedback) |
@@ -542,6 +543,7 @@ The raw element or pattern on the left already has a design-system component. Wr
542
543
  | `section with a border, a heading row and a padded body` | [SectionCard](#layout) |
543
544
  | `six <input maxlength="1">` | [OTPInput](#forms) |
544
545
  | `stack of <details>` | [Accordion](#layout) |
546
+ | `SVG data-URL icons` | [Icons](#icons) |
545
547
  | `title="…"` | [Tooltip](#overlays) |
546
548
  | `two <input type="date">` | [DateRangePicker](#forms) |
547
549
  | `two-column label/value divs` | [MetadataGrid](#data-display) |
@@ -455,14 +455,18 @@
455
455
  "pattern": "Icon$",
456
456
  "exclude": "BrandIcon$",
457
457
  "helpers": [
458
- "createIcon"
458
+ "createIcon",
459
+ "iconCursor",
460
+ "iconToSvg"
459
461
  ],
460
- "description": "The UI glyph catalogue: Lucide glyphs on the 24-unit grid, `currentColor` strokes at 2, `aria-hidden` unless titled. Import from `@assure-one/design-system/icons` (server-safe) or the root.",
461
- "useWhen": "Any glyph in a control, a list row, a status, an empty state; `createIcon` to register a one-off glyph in the same contract.",
462
+ "description": "The UI glyph catalogue and the only UI icon source: Lucide glyphs on the 24-unit grid, `currentColor` strokes at 2, `aria-hidden` unless titled. Import from `@assure-one/design-system/icons` (server-safe) or the root. `iconToSvg` / `iconCursor` give the same Lucide glyph as an SVG string or a CSS cursor where React cannot render.",
463
+ "useWhen": "Any glyph in a control, a list row, a status, an empty state; `iconToSvg` in a non-React shell, `iconCursor` for a tool-mode cursor. Products never draw their own icons (`createIcon` is the catalogue's builder).",
462
464
  "dontUseWhen": "You need a product mark (brand icon family or `Logo`), a state-driven glyph (`StatusIcon`, `PriorityIcon`) or an illustration.",
463
465
  "replacesRawHtml": [
464
466
  "<svg>",
465
- "lucide-react"
467
+ "lucide-react",
468
+ "icon characters (✓ ✕ → ↗)",
469
+ "SVG data-URL icons"
466
470
  ],
467
471
  "story": "src/primitives/icons.stories.tsx",
468
472
  "status": "experimental"
@@ -57,7 +57,7 @@ If a UI element exists in the design system, use it. Do not write `<button>`, `<
57
57
  | `<table>` | `Table`, `DataTable`, `DataTableView` |
58
58
  | `<h1>`–`<h6>`, `<p>`, `<code>`, `<kbd>` | `Heading`, `Text`, `Code`, `Kbd` |
59
59
  | `<hr>` | `Separator` |
60
- | `<svg>`, `lucide-react` | the icon catalogue (`/icons`) |
60
+ | `<svg>`, `lucide`/`lucide-react` or any icon package, `✓ ✕ → ↗` as icons | the Lucide icon catalogue (`/icons`); `iconToSvg`/`iconCursor` outside React |
61
61
  | `flex gap-*` wrappers | `Stack`, `Inline`; `Surface` / `Card` for boxes |
62
62
  | `title="…"`, `window.confirm()`, `alert()` | `Tooltip`, `ConfirmActionButton` / `AlertDialog`, `useToast` |
63
63
 
@@ -179,6 +179,243 @@ const noDsTokenRedefinition = {
179
179
  },
180
180
  };
181
181
 
182
+ /* ─── Icons: Lucide, through the design system only ───────────────────── */
183
+
184
+ /**
185
+ * Icon packages a product must not import: every UI icon is a Lucide glyph
186
+ * from `@assure-one/design-system/icons`, and the design system alone owns
187
+ * the Lucide dependency. Includes flag sets (a symbol library by another name).
188
+ */
189
+ const ICON_PACKAGES = [
190
+ /^lucide(?:-react|-static|-vue|-svelte|-preact)?(?:\/|$)/,
191
+ /^@lucide\//,
192
+ /^react-icons(?:\/|$)/,
193
+ /^@?heroicons(?:\/|$)/,
194
+ /^@radix-ui\/react-icons$/,
195
+ /^@tabler\/icons/,
196
+ /^@phosphor-icons\//,
197
+ /^phosphor-react$/,
198
+ /^@fortawesome\//,
199
+ /^(?:react-)?feather(?:-icons)?$/,
200
+ /^@mui\/icons-material/,
201
+ /^iconoir/,
202
+ /^@iconify\//,
203
+ /^remixicon/,
204
+ /^@primer\/octicons/,
205
+ /^bootstrap-icons/,
206
+ /^ionicons/,
207
+ /^@ant-design\/icons/,
208
+ /^@carbon\/icons/,
209
+ /^simple-icons/,
210
+ /^country-flag-icons(?:\/|$)/,
211
+ /^react-country-flag$/,
212
+ /^flag-icons/,
213
+ ];
214
+ /** An SVG file imported as a module (svgr `?react`, a URL import, a component import). */
215
+ const SVG_MODULE = /\.svg(?:\?.*)?$/;
216
+
217
+ /** @type {import("eslint").Rule.RuleModule} */
218
+ const noIconLibraries = {
219
+ meta: {
220
+ type: "problem",
221
+ docs: {
222
+ description:
223
+ "Import icons from @assure-one/design-system/icons — never from Lucide or another icon package, and never an .svg module.",
224
+ },
225
+ schema: [],
226
+ messages: {
227
+ library:
228
+ '"{{ source }}" is an icon source outside the design system. UI icons are Lucide glyphs from "@assure-one/design-system/icons"; a missing glyph is a design-system PR.',
229
+ svgModule:
230
+ '"{{ source }}" imports an SVG file as an icon. Use a named icon from "@assure-one/design-system/icons" (a brand mark is a DS *BrandIcon or a static <img> asset).',
231
+ },
232
+ },
233
+ create(context) {
234
+ return importVisitors((node, source) => {
235
+ if (!source) return;
236
+ if (ICON_PACKAGES.some((re) => re.test(source))) {
237
+ context.report({ node, messageId: "library", data: { source } });
238
+ } else if (SVG_MODULE.test(source)) {
239
+ context.report({ node, messageId: "svgModule", data: { source } });
240
+ }
241
+ });
242
+ },
243
+ };
244
+
245
+ /**
246
+ * `ds-svg-content: <kind> — <why>` in a comment declares that a file draws
247
+ * SVG that is not a UI icon. Kinds: a chart or data mark, an illustration or
248
+ * preview, a document/annotation drawing, a decorative pattern, or a brand
249
+ * mark that is still waiting on a decision (tracked, never a UI icon).
250
+ */
251
+ const SVG_CONTENT_DIRECTIVE =
252
+ /ds-svg-content:\s*(chart|illustration|document|pattern|brand)\b\s*(?:—|-|–|:)\s*\S/;
253
+ /** Box sizes (px or Tailwind steps) that make an `<svg>` an icon, whatever the file says. */
254
+ const ICON_STEP = String.raw`(?:2|2\.5|3|3\.5|4|4\.5|5|5\.5|6|7|8)(?:\s|$)`;
255
+ const iconBoxClass = (axis) => new RegExp(String.raw`(?:^|\s)(?:[a-z0-9-]+:)*${axis}-${ICON_STEP}`);
256
+ const MAX_ICON_PX = 32;
257
+
258
+ function jsxAttribute(opening, name) {
259
+ return opening.attributes.find((a) => a.type === "JSXAttribute" && a.name?.name === name);
260
+ }
261
+
262
+ function staticValue(attribute) {
263
+ const value = attribute?.value;
264
+ if (!value) return undefined;
265
+ if (value.type === "Literal") return value.value;
266
+ if (value.type === "JSXExpressionContainer") {
267
+ const e = value.expression;
268
+ if (e.type === "Literal") return e.value;
269
+ if (e.type === "TemplateLiteral" && e.expressions.length === 0) return e.quasis[0].value.cooked;
270
+ }
271
+ return undefined;
272
+ }
273
+
274
+ function isIconSized(opening) {
275
+ const px = ["width", "height"]
276
+ .map((name) => Number.parseFloat(String(staticValue(jsxAttribute(opening, name)) ?? "")))
277
+ .filter((n) => Number.isFinite(n));
278
+ if (px.length > 0 && px.every((n) => n <= MAX_ICON_PX)) return true;
279
+ const className = staticValue(jsxAttribute(opening, "className"));
280
+ if (typeof className !== "string") return false;
281
+ return (
282
+ iconBoxClass("size").test(className) ||
283
+ (iconBoxClass("h").test(className) && iconBoxClass("w").test(className))
284
+ );
285
+ }
286
+
287
+ /** @type {import("eslint").Rule.RuleModule} */
288
+ const noAuthoredSvg = {
289
+ meta: {
290
+ type: "problem",
291
+ docs: {
292
+ description:
293
+ "No authored SVG icons in a product: draw icons with the design system's Lucide catalogue; SVG content (charts, illustrations, document drawings, patterns) declares itself with `ds-svg-content:`.",
294
+ },
295
+ schema: [],
296
+ messages: {
297
+ undeclared:
298
+ "Inline <svg> in a product. A UI icon is a named icon from \"@assure-one/design-system/icons\". If this SVG is content — a chart, an illustration, a document drawing or a pattern — say so in a `ds-svg-content: <kind> — <why>` comment in this file.",
299
+ iconSized:
300
+ "This <svg> is icon-sized, and an icon is a named icon from \"@assure-one/design-system/icons\" even in a file that draws SVG content. Brand marks declare `ds-svg-content: brand`.",
301
+ createIcon:
302
+ "createIcon() builds a custom icon. Products use the Lucide catalogue from \"@assure-one/design-system/icons\"; a missing glyph is a design-system PR.",
303
+ markup:
304
+ "SVG markup in a string is an authored icon source. Use a named icon (or iconToSvg/iconCursor) from \"@assure-one/design-system/icons\".",
305
+ },
306
+ },
307
+ create(context) {
308
+ const source = context.sourceCode ?? context.getSourceCode();
309
+ const declared = source
310
+ .getAllComments()
311
+ .map((c) => c.value.match(SVG_CONTENT_DIRECTIVE)?.[1])
312
+ .find(Boolean);
313
+ return {
314
+ JSXOpeningElement(node) {
315
+ if (node.name?.type !== "JSXIdentifier" || node.name.name !== "svg") return;
316
+ if (!declared) {
317
+ context.report({ node, messageId: "undeclared" });
318
+ } else if (declared !== "brand" && isIconSized(node)) {
319
+ context.report({ node, messageId: "iconSized" });
320
+ }
321
+ },
322
+ CallExpression(node) {
323
+ if (node.callee.type === "Identifier" && node.callee.name === "createIcon") {
324
+ context.report({ node, messageId: "createIcon" });
325
+ }
326
+ },
327
+ Literal(node) {
328
+ if (declared || typeof node.value !== "string") return;
329
+ if (/<svg[\s>]|data:image\/svg\+xml/i.test(node.value)) context.report({ node, messageId: "markup" });
330
+ },
331
+ TemplateElement(node) {
332
+ if (declared) return;
333
+ if (/<svg[\s>]|data:image\/svg\+xml/i.test(node.value.cooked ?? "")) {
334
+ context.report({ node, messageId: "markup" });
335
+ }
336
+ },
337
+ };
338
+ },
339
+ };
340
+
341
+ /**
342
+ * Characters that products have used as stand-in icons: arrows, ticks,
343
+ * crosses, stars, eyes, clocks. `×` is left out on purpose — it is the
344
+ * multiplication and dimension sign far more often than a close button.
345
+ */
346
+ const GLYPH = "[✓✔✗✘✕✖▾▴▸◂▼▲►◄▶◀⏵⏷↑↓←→↗↘↙↖⇅⇄↩↪⋯⋮⚠ℹ★☆◉◎✎✏⟳↻↺⏱⌄⌃⨯⬆⬇➔➜➝➞]";
347
+ const ONLY_GLYPHS = new RegExp(`^(?:${GLYPH}|\\p{Extended_Pictographic}|\\uFE0F|\\s)+$`, "u");
348
+ const EDGE_GLYPH = new RegExp(`^${GLYPH}\\s|\\s${GLYPH}$`, "u");
349
+ /** Elements whose text is a keyboard legend, where ↵ ⌘ ↑ are the key names. */
350
+ const KEY_ELEMENTS = new Set(["Kbd", "KbdHint", "kbd", "Shortcut"]);
351
+ const ICON_KEYS = new Set(["icon", "glyph", "symbol"]);
352
+
353
+ function insideKeyLegend(node) {
354
+ for (let n = node.parent; n; n = n.parent) {
355
+ if (n.type === "JSXElement") {
356
+ const name = n.openingElement.name;
357
+ if (name?.type === "JSXIdentifier" && KEY_ELEMENTS.has(name.name)) return true;
358
+ }
359
+ }
360
+ return false;
361
+ }
362
+
363
+ function glyphText(text) {
364
+ const trimmed = text.trim();
365
+ if (!trimmed) return false;
366
+ return ONLY_GLYPHS.test(trimmed) || EDGE_GLYPH.test(trimmed);
367
+ }
368
+
369
+ /** A string that reaches JSX children directly (maybe through `?:` / `&&` / `||`). */
370
+ function rendersAsChild(node) {
371
+ let n = node.parent;
372
+ while (n && (n.type === "ConditionalExpression" || n.type === "LogicalExpression")) n = n.parent;
373
+ return n?.type === "JSXExpressionContainer" && n.parent?.type === "JSXElement";
374
+ }
375
+
376
+ /** @type {import("eslint").Rule.RuleModule} */
377
+ const noGlyphIcons = {
378
+ meta: {
379
+ type: "problem",
380
+ docs: {
381
+ description:
382
+ "Render icons, not characters: ✓ ✕ ↗ → ★ ⏱ standing in for an icon become a Lucide icon from the design system.",
383
+ },
384
+ schema: [],
385
+ messages: {
386
+ glyph:
387
+ '"{{ text }}" uses a character as an icon. Render the matching icon from "@assure-one/design-system/icons" (e.g. CheckIcon, XIcon, ArrowRightIcon, ArrowUpRightIcon) — keep the text and give the icon aria-hidden, or an accessible name if it stands alone. Keyboard legends belong in <Kbd>.',
388
+ },
389
+ },
390
+ create(context) {
391
+ const report = (node, text) =>
392
+ context.report({ node, messageId: "glyph", data: { text: text.trim().slice(0, 40) } });
393
+ return {
394
+ JSXText(node) {
395
+ if (glyphText(node.value) && !insideKeyLegend(node)) report(node, node.value);
396
+ },
397
+ Literal(node) {
398
+ if (typeof node.value !== "string" || !glyphText(node.value)) return;
399
+ const parent = node.parent;
400
+ const iconProperty =
401
+ parent?.type === "Property" &&
402
+ parent.value === node &&
403
+ ICON_KEYS.has(parent.key?.name ?? parent.key?.value) &&
404
+ ONLY_GLYPHS.test(node.value.trim());
405
+ if (iconProperty || (rendersAsChild(node) && !insideKeyLegend(node))) report(node, node.value);
406
+ },
407
+ TemplateLiteral(node) {
408
+ if (!rendersAsChild(node) || insideKeyLegend(node)) return;
409
+ const first = node.quasis[0].value.cooked ?? "";
410
+ const last = node.quasis[node.quasis.length - 1].value.cooked ?? "";
411
+ if (new RegExp(`^${GLYPH}\\s`, "u").test(first) || new RegExp(`\\s${GLYPH}$`, "u").test(last)) {
412
+ report(node, `${first}…${last}`);
413
+ }
414
+ },
415
+ };
416
+ },
417
+ };
418
+
182
419
  /** The `ds` plugin: the rules alone, for a config that wires them itself. */
183
420
  export const plugin = {
184
421
  meta: { name: "@assure-one/design-system/eslint-config" },
@@ -187,6 +424,9 @@ export const plugin = {
187
424
  "no-radix-imports": noRadixImports,
188
425
  "no-deep-imports": noDeepImports,
189
426
  "no-ds-token-redefinition": noDsTokenRedefinition,
427
+ "no-icon-libraries": noIconLibraries,
428
+ "no-authored-svg": noAuthoredSvg,
429
+ "no-glyph-icons": noGlyphIcons,
190
430
  },
191
431
  };
192
432
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assure-one/design-system",
3
- "version": "1.43.1",
3
+ "version": "1.44.0",
4
4
  "description": "Assure One design system — tokens, primitives, and patterns for the Assure Suite.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,