@assure-one/design-system 1.43.0 → 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.
- package/dist/{chunk-TYXT5C2B.js → chunk-4TXBY6P7.js} +20 -44
- package/dist/chunk-4TXBY6P7.js.map +1 -0
- package/dist/{chunk-TBDTXAFH.js → chunk-E2URQD5A.js} +152 -121
- package/dist/chunk-E2URQD5A.js.map +1 -0
- package/dist/{chunk-LUWPEBXZ.js → chunk-PC6M4QZ2.js} +62 -93
- package/dist/chunk-PC6M4QZ2.js.map +1 -0
- package/dist/{chunk-XJKCKBHI.js → chunk-Z7QPHPNV.js} +28 -6
- package/dist/chunk-Z7QPHPNV.js.map +1 -0
- package/dist/css/components.css +1 -1
- package/dist/icons/index.d.ts +135 -17
- package/dist/icons/index.js +1 -1
- package/dist/index.d.ts +29 -11
- package/dist/index.js +129 -210
- package/dist/index.js.map +1 -1
- package/dist/legacy/index.js +3 -3
- package/dist/styles.css +1 -1
- package/dist/suite/index.js +3 -3
- package/docs/components.md +7 -5
- package/docs/components.registry.json +8 -4
- package/docs/for-ai-agents.md +1 -1
- package/eslint-config/index.mjs +240 -0
- package/package.json +1 -1
- package/dist/chunk-LUWPEBXZ.js.map +0 -1
- package/dist/chunk-TBDTXAFH.js.map +0 -1
- package/dist/chunk-TYXT5C2B.js.map +0 -1
- package/dist/chunk-XJKCKBHI.js.map +0 -1
package/dist/suite/index.js
CHANGED
|
@@ -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-
|
|
3
|
-
export { DocumentDetailActions, DocumentDetailBody, DocumentDetailHeader, DocumentDetailMetaRow, DocumentDetailPanel, DocumentDetailRequester, DocumentDetailTitle, DocumentList, DocumentListSection, DocumentRow } from '../chunk-
|
|
4
|
-
import '../chunk-
|
|
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
|
package/docs/components.md
CHANGED
|
@@ -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: **
|
|
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.
|
|
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; `
|
|
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; `
|
|
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"
|
package/docs/for-ai-agents.md
CHANGED
|
@@ -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`
|
|
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
|
|
package/eslint-config/index.mjs
CHANGED
|
@@ -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
|
|