@adea-ai/ui 0.77.0 → 0.78.1
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/components/ui/status-chip/status-chip.js +6 -3
- package/dist/components/ui/status-chip/status-chip.js.map +1 -1
- package/dist/lint/index.d.ts +34 -0
- package/dist/lint/index.d.ts.map +1 -0
- package/dist/lint.js +130 -0
- package/dist/lint.js.map +1 -0
- package/dist/r/src/components/ui/status-chip/status-chip.tsx +1 -1
- package/package.json +5 -1
- package/src/components/ui/status-chip/status-chip.tsx +1 -1
- package/src/lint/index.ts +179 -0
|
@@ -83,9 +83,12 @@ function StatusChip(props) {
|
|
|
83
83
|
as: "span",
|
|
84
84
|
"class": "inline-flex",
|
|
85
85
|
children: chip
|
|
86
|
-
}), createComponent(TooltipContent, {
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
}), createComponent(TooltipContent, {
|
|
87
|
+
hideArrow: true,
|
|
88
|
+
get children() {
|
|
89
|
+
return detail();
|
|
90
|
+
}
|
|
91
|
+
})];
|
|
89
92
|
} })
|
|
90
93
|
});
|
|
91
94
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status-chip.js","names":["Show","splitProps","ComponentProps","JSX","cva","cn","Tooltip","TooltipContent","TooltipTrigger","statusDotVariants","variants","tone","neutral","success","warning","danger","info","unknown","defaultVariants","labelTone","Record","StatusTone","StatusChipProps","Omit","label","detail","trailing","Element","StatusChip","props","local","rest","chip","_el$","_tmpl$","_el$2","firstChild","_$spread","_$mergeProps","class","_$insert","_$effect","_$className","_$createComponent","when","fallback","children","as","StatusEntry","Readonly","id","name","StatusList","entries","compact","_el$3","_tmpl$2","map","entry","_el$4","_tmpl$4","_el$6","_el$5","_tmpl$3","_$mergeProps","_$className","_$createComponent"],"sources":["../../../../src/components/ui/status-chip/status-chip.tsx"],"sourcesContent":["import { Show, splitProps, type ComponentProps, type JSX } from 'solid-js'\nimport { cva } from '#lib/variants'\nimport { cn } from '#lib/utils'\nimport { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip'\n\n/**\n * StatusChip.\n *\n * A state, said in one word, with the reason available on hover. It exists\n * because a status readout has three parts that are always the same three parts —\n * a tone, a label, and the detail that justifies the label — and rendering them\n * ad hoc produces the two failures this component is here to prevent: a colour\n * with no text (unreadable to anyone who cannot see it) and a bare word with no\n * explanation (\"Needs configuration\" — of what?).\n *\n * The tones are deliberately not a rainbow. Six cover what a status can actually\n * mean:\n *\n * - `neutral` — fine, and there is nothing to say about it.\n * - `success` — a check passed, a capability is ready, a job finished.\n * - `warning` — degraded, or needs a decision from the user.\n * - `danger` — blocked, failed, unrecoverable without intervention.\n * - `info` — in flight or scheduled; work is happening, nothing is wrong.\n * - `unknown` — the honest absence of information.\n *\n * `unknown` is not the same as `neutral`, and collapsing them is the mistake\n * worth avoiding: \"this is idle\" and \"we have not been told yet\" are different\n * facts, and a product that shows the second as the first is lying quietly. The\n * ring is hollow so it also reads as different in greyscale.\n *\n * The dot is `aria-hidden` and the label carries the meaning, so a tone is never\n * the only signal.\n */\nexport const statusDotVariants = cva('size-2 shrink-0 rounded-full', {\n variants: {\n tone: {\n neutral: 'bg-muted-foreground',\n success: 'bg-success',\n warning: 'bg-warning',\n danger: 'bg-destructive',\n info: 'bg-info',\n unknown: 'border border-muted-foreground bg-transparent',\n },\n },\n defaultVariants: { tone: 'neutral' },\n})\n\nconst labelTone: Record<StatusTone, string> = {\n neutral: 'text-foreground',\n success: 'text-foreground',\n warning: 'text-foreground',\n danger: 'text-foreground',\n info: 'text-foreground',\n unknown: 'text-muted-foreground',\n}\n\nexport type StatusTone = 'neutral' | 'success' | 'warning' | 'danger' | 'info' | 'unknown'\n\nexport type StatusChipProps = Omit<ComponentProps<'span'>, 'children'> & {\n tone?: StatusTone\n label: string\n /** Why the status is what it is. Shown in a tooltip; omit for a self-evident label. */\n detail?: string\n /** A trailing slot: a timestamp, a retry affordance, a count. */\n trailing?: JSX.Element\n}\n\nexport function StatusChip(props: StatusChipProps) {\n const [local, rest] = splitProps(props, ['tone', 'label', 'detail', 'trailing', 'class'])\n\n const chip = (\n <span\n class={cn(\n 'inline-flex w-fit items-center gap-1.5 text-xs font-medium whitespace-nowrap',\n labelTone[local.tone ?? 'neutral'],\n local.class\n )}\n {...rest}\n >\n <span class={statusDotVariants({ tone: local.tone })} aria-hidden=\"true\" />\n {local.label}\n {local.trailing}\n </span>\n )\n\n return (\n <Show when={local.detail} fallback={chip}>\n {(detail) => (\n <Tooltip>\n <TooltipTrigger as=\"span\" class=\"inline-flex\">\n {chip}\n </TooltipTrigger>\n <TooltipContent>{detail()}</TooltipContent>\n </Tooltip>\n )}\n </Show>\n )\n}\n\nexport type StatusEntry = Readonly<{\n /** A stable key for the row. */\n id: string\n /** What is being reported — \"Configuration\", \"Runtime\", \"Sync\". */\n name: string\n tone: StatusTone\n label: string\n detail?: string\n}>\n\n/**\n * StatusList.\n *\n * The several-facts-about-one-thing shape: an agent with a configuration state, a\n * runtime state and an activity state; a service with a build, a deploy and a\n * health check. It is a definition list rather than a table because there is no\n * column to sort or compare — the rows are facets of a single subject, and a\n * screen reader should hear them as such.\n *\n * Fixed width on the name column is what makes a column of these scannable; the\n * rows line up without a table's semantics.\n */\nexport function StatusList(props: {\n entries: readonly StatusEntry[]\n class?: string\n /** Hide the name column and show only the chips — for a compact badge strip. */\n compact?: boolean\n}) {\n return (\n <dl class={cn('grid gap-2 text-sm', props.class)}>\n {props.entries.map((entry) => (\n <div class=\"flex items-baseline justify-between gap-4\">\n <Show when={!props.compact}>\n <dt class=\"text-muted-foreground\">{entry.name}</dt>\n </Show>\n <dd class={cn(!props.compact && 'shrink-0')}>\n <StatusChip tone={entry.tone} label={entry.label} detail={entry.detail} />\n </dd>\n </div>\n ))}\n </dl>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,IAAaS,oBAAoBL,IAAI,gCAAgC;CACnEM,UAAU,EACRC,MAAM;EACJC,SAAS;EACTC,SAAS;EACTC,SAAS;EACTC,QAAQ;EACRC,MAAM;EACNC,SAAS;CACX,EACF;CACAC,iBAAiB,EAAEP,MAAM,UAAU;AACrC,CAAC;AAED,IAAMQ,YAAwC;CAC5CP,SAAS;CACTC,SAAS;CACTC,SAAS;CACTC,QAAQ;CACRC,MAAM;CACNC,SAAS;AACX;AAaA,SAAgBW,WAAWC,OAAwB;CACjD,MAAM,CAACC,OAAOC,QAAQ9B,WAAW4B,OAAO;EAAC;EAAQ;EAAS;EAAU;EAAY;CAAO,CAAC;CAExF,MAAMG,cAAI;EAAA,IAAAC,OAAAC,OAAA,GAAAC,QAAAF,KAAAG;EAAAC,OAAAJ,MAAAK,WAAA,EAAA,KAAA,WAAA;GAAA,OAECjC,GACL,gFACAc,UAAUW,MAAMnB,QAAQ,YACxBmB,MAAMS,KACR;EAAC,EAAA,GACGR,IAAI,GAAA,OAAA,IAAA;EAAAS,OAAAP,YAGPH,MAAMN,OAAK,IAAA;EAAAgB,OAAAP,YACXH,MAAMJ,UAAQ,IAAA;EAAAe,aAAAC,UAAAP,OAFF1B,kBAAkB,EAAEE,MAAMmB,MAAMnB,KAAK,CAAC,CAAC,CAAA;EAAA,OAAAsB;CAAA,EAAA,CAAA;CAMxD,OAAAU,gBACG3C,MAAI;EAAA,IAAC4C,OAAI;GAAA,OAAEd,MAAML;EAAM;EAAEoB,UAAUb;EAAIc,WACpCrB,WAAMkB,gBACLrC,SAAO,EAAA,IAAAwC,WAAA;GAAA,OAAA,CAAAH,gBACLnC,gBAAc;IAACuC,IAAE;IAAA,SAAA;IAAAD,UACfd;GAAI,CAAA,GAAAW,gBAENpC,gBAAc,
|
|
1
|
+
{"version":3,"file":"status-chip.js","names":["Show","splitProps","ComponentProps","JSX","cva","cn","Tooltip","TooltipContent","TooltipTrigger","statusDotVariants","variants","tone","neutral","success","warning","danger","info","unknown","defaultVariants","labelTone","Record","StatusTone","StatusChipProps","Omit","label","detail","trailing","Element","StatusChip","props","local","rest","chip","_el$","_tmpl$","_el$2","firstChild","_$spread","_$mergeProps","class","_$insert","_$effect","_$className","_$createComponent","when","fallback","children","as","hideArrow","StatusEntry","Readonly","id","name","StatusList","entries","compact","_el$3","_tmpl$2","map","entry","_el$4","_tmpl$4","_el$6","_el$5","_tmpl$3","_$mergeProps","_$className","_$createComponent"],"sources":["../../../../src/components/ui/status-chip/status-chip.tsx"],"sourcesContent":["import { Show, splitProps, type ComponentProps, type JSX } from 'solid-js'\nimport { cva } from '#lib/variants'\nimport { cn } from '#lib/utils'\nimport { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip'\n\n/**\n * StatusChip.\n *\n * A state, said in one word, with the reason available on hover. It exists\n * because a status readout has three parts that are always the same three parts —\n * a tone, a label, and the detail that justifies the label — and rendering them\n * ad hoc produces the two failures this component is here to prevent: a colour\n * with no text (unreadable to anyone who cannot see it) and a bare word with no\n * explanation (\"Needs configuration\" — of what?).\n *\n * The tones are deliberately not a rainbow. Six cover what a status can actually\n * mean:\n *\n * - `neutral` — fine, and there is nothing to say about it.\n * - `success` — a check passed, a capability is ready, a job finished.\n * - `warning` — degraded, or needs a decision from the user.\n * - `danger` — blocked, failed, unrecoverable without intervention.\n * - `info` — in flight or scheduled; work is happening, nothing is wrong.\n * - `unknown` — the honest absence of information.\n *\n * `unknown` is not the same as `neutral`, and collapsing them is the mistake\n * worth avoiding: \"this is idle\" and \"we have not been told yet\" are different\n * facts, and a product that shows the second as the first is lying quietly. The\n * ring is hollow so it also reads as different in greyscale.\n *\n * The dot is `aria-hidden` and the label carries the meaning, so a tone is never\n * the only signal.\n */\nexport const statusDotVariants = cva('size-2 shrink-0 rounded-full', {\n variants: {\n tone: {\n neutral: 'bg-muted-foreground',\n success: 'bg-success',\n warning: 'bg-warning',\n danger: 'bg-destructive',\n info: 'bg-info',\n unknown: 'border border-muted-foreground bg-transparent',\n },\n },\n defaultVariants: { tone: 'neutral' },\n})\n\nconst labelTone: Record<StatusTone, string> = {\n neutral: 'text-foreground',\n success: 'text-foreground',\n warning: 'text-foreground',\n danger: 'text-foreground',\n info: 'text-foreground',\n unknown: 'text-muted-foreground',\n}\n\nexport type StatusTone = 'neutral' | 'success' | 'warning' | 'danger' | 'info' | 'unknown'\n\nexport type StatusChipProps = Omit<ComponentProps<'span'>, 'children'> & {\n tone?: StatusTone\n label: string\n /** Why the status is what it is. Shown in a tooltip; omit for a self-evident label. */\n detail?: string\n /** A trailing slot: a timestamp, a retry affordance, a count. */\n trailing?: JSX.Element\n}\n\nexport function StatusChip(props: StatusChipProps) {\n const [local, rest] = splitProps(props, ['tone', 'label', 'detail', 'trailing', 'class'])\n\n const chip = (\n <span\n class={cn(\n 'inline-flex w-fit items-center gap-1.5 text-xs font-medium whitespace-nowrap',\n labelTone[local.tone ?? 'neutral'],\n local.class\n )}\n {...rest}\n >\n <span class={statusDotVariants({ tone: local.tone })} aria-hidden=\"true\" />\n {local.label}\n {local.trailing}\n </span>\n )\n\n return (\n <Show when={local.detail} fallback={chip}>\n {(detail) => (\n <Tooltip>\n <TooltipTrigger as=\"span\" class=\"inline-flex\">\n {chip}\n </TooltipTrigger>\n <TooltipContent hideArrow>{detail()}</TooltipContent>\n </Tooltip>\n )}\n </Show>\n )\n}\n\nexport type StatusEntry = Readonly<{\n /** A stable key for the row. */\n id: string\n /** What is being reported — \"Configuration\", \"Runtime\", \"Sync\". */\n name: string\n tone: StatusTone\n label: string\n detail?: string\n}>\n\n/**\n * StatusList.\n *\n * The several-facts-about-one-thing shape: an agent with a configuration state, a\n * runtime state and an activity state; a service with a build, a deploy and a\n * health check. It is a definition list rather than a table because there is no\n * column to sort or compare — the rows are facets of a single subject, and a\n * screen reader should hear them as such.\n *\n * Fixed width on the name column is what makes a column of these scannable; the\n * rows line up without a table's semantics.\n */\nexport function StatusList(props: {\n entries: readonly StatusEntry[]\n class?: string\n /** Hide the name column and show only the chips — for a compact badge strip. */\n compact?: boolean\n}) {\n return (\n <dl class={cn('grid gap-2 text-sm', props.class)}>\n {props.entries.map((entry) => (\n <div class=\"flex items-baseline justify-between gap-4\">\n <Show when={!props.compact}>\n <dt class=\"text-muted-foreground\">{entry.name}</dt>\n </Show>\n <dd class={cn(!props.compact && 'shrink-0')}>\n <StatusChip tone={entry.tone} label={entry.label} detail={entry.detail} />\n </dd>\n </div>\n ))}\n </dl>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,IAAaS,oBAAoBL,IAAI,gCAAgC;CACnEM,UAAU,EACRC,MAAM;EACJC,SAAS;EACTC,SAAS;EACTC,SAAS;EACTC,QAAQ;EACRC,MAAM;EACNC,SAAS;CACX,EACF;CACAC,iBAAiB,EAAEP,MAAM,UAAU;AACrC,CAAC;AAED,IAAMQ,YAAwC;CAC5CP,SAAS;CACTC,SAAS;CACTC,SAAS;CACTC,QAAQ;CACRC,MAAM;CACNC,SAAS;AACX;AAaA,SAAgBW,WAAWC,OAAwB;CACjD,MAAM,CAACC,OAAOC,QAAQ9B,WAAW4B,OAAO;EAAC;EAAQ;EAAS;EAAU;EAAY;CAAO,CAAC;CAExF,MAAMG,cAAI;EAAA,IAAAC,OAAAC,OAAA,GAAAC,QAAAF,KAAAG;EAAAC,OAAAJ,MAAAK,WAAA,EAAA,KAAA,WAAA;GAAA,OAECjC,GACL,gFACAc,UAAUW,MAAMnB,QAAQ,YACxBmB,MAAMS,KACR;EAAC,EAAA,GACGR,IAAI,GAAA,OAAA,IAAA;EAAAS,OAAAP,YAGPH,MAAMN,OAAK,IAAA;EAAAgB,OAAAP,YACXH,MAAMJ,UAAQ,IAAA;EAAAe,aAAAC,UAAAP,OAFF1B,kBAAkB,EAAEE,MAAMmB,MAAMnB,KAAK,CAAC,CAAC,CAAA;EAAA,OAAAsB;CAAA,EAAA,CAAA;CAMxD,OAAAU,gBACG3C,MAAI;EAAA,IAAC4C,OAAI;GAAA,OAAEd,MAAML;EAAM;EAAEoB,UAAUb;EAAIc,WACpCrB,WAAMkB,gBACLrC,SAAO,EAAA,IAAAwC,WAAA;GAAA,OAAA,CAAAH,gBACLnC,gBAAc;IAACuC,IAAE;IAAA,SAAA;IAAAD,UACfd;GAAI,CAAA,GAAAW,gBAENpC,gBAAc;IAACyC,WAAS;IAAA,IAAAF,WAAA;KAAA,OAAErB,OAAO;IAAC;GAAA,CAAA,CAAA;EAAA,EAAA,CAAA;CAEtC,CAAA;AAGP;;;;;;;;;;;;;AAwBA,SAAgB4B,WAAWxB,OAKxB;CACD,cAAA;EAAA,IAAA2B,QAAAC,QAAA;EAAAjB,OAAAgB,aAEK3B,MAAMyB,QAAQI,KAAKC,iBAAK;GAAA,IAAAC,QAAAC,QAAA,GAAAC,QAAAF,MAAAxB;GAAAI,OAAAoB,OAAAjB,gBAEpB3C,MAAI;IAAA,IAAC4C,OAAI;KAAA,OAAE,CAACf,MAAM0B;IAAO;IAAA,IAAAT,WAAA;KAAA,IAAAiB,QAAAC,QAAA;KAAAxB,OAAAuB,aACWJ,MAAMP,IAAI;KAAA,OAAAW;IAAA;GAAA,CAAA,GAAAD,KAAA;GAAAtB,OAAAsB,OAAAnB,gBAG5Cf,YAAU;IAAA,IAACjB,OAAI;KAAA,OAAEgD,MAAMhD;IAAI;IAAA,IAAEa,QAAK;KAAA,OAAEmC,MAAMnC;IAAK;IAAA,IAAEC,SAAM;KAAA,OAAEkC,MAAMlC;IAAM;GAAA,CAAA,CAAA;GAAAgB,aAAAC,UAAAoB,OAD7DzD,GAAG,CAACwB,MAAM0B,WAAW,UAAU,CAAC,CAAA;GAAA,OAAAK;EAAA,EAAA,CAAA,CAI9C,CAAC;EAAAnB,aAAAC,UAAAc,OAVOnD,GAAG,sBAAsBwB,MAAMU,KAAK,CAAC,CAAA;EAAA,OAAAiB;CAAA,EAAA,CAAA;AAapD"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
type Json = string | number | boolean | null | Json[] | {
|
|
2
|
+
[key: string]: Json;
|
|
3
|
+
};
|
|
4
|
+
type RuleModule = {
|
|
5
|
+
meta: {
|
|
6
|
+
type: 'problem';
|
|
7
|
+
docs: {
|
|
8
|
+
description: string;
|
|
9
|
+
};
|
|
10
|
+
schema: Json[];
|
|
11
|
+
messages: Record<string, string>;
|
|
12
|
+
};
|
|
13
|
+
create: (context: LintContext) => Record<string, (node: any) => void>;
|
|
14
|
+
};
|
|
15
|
+
interface LintContext {
|
|
16
|
+
options?: unknown[];
|
|
17
|
+
report: (descriptor: {
|
|
18
|
+
node: unknown;
|
|
19
|
+
messageId?: string;
|
|
20
|
+
message?: string;
|
|
21
|
+
data?: Record<string, Json>;
|
|
22
|
+
}) => void;
|
|
23
|
+
}
|
|
24
|
+
declare const plugin: {
|
|
25
|
+
meta: {
|
|
26
|
+
name: string;
|
|
27
|
+
};
|
|
28
|
+
rules: {
|
|
29
|
+
'no-raw-interactive-elements': RuleModule;
|
|
30
|
+
'no-primitive-library-imports': RuleModule;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
export default plugin;
|
|
34
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lint/index.ts"],"names":[],"mappings":"AAmBA,KAAK,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,IAAI,EAAE,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAA;AAa/E,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS,CAAA;QACf,IAAI,EAAE;YAAE,WAAW,EAAE,MAAM,CAAA;SAAE,CAAA;QAC7B,MAAM,EAAE,IAAI,EAAE,CAAA;QACd,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KACjC,CAAA;IACD,MAAM,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC,CAAA;CACtE,CAAA;AAED,UAAU,WAAW;IACnB,OAAO,CAAC,EAAE,OAAO,EAAE,CAAA;IACnB,MAAM,EAAE,CAAC,UAAU,EAAE;QACnB,IAAI,EAAE,OAAO,CAAA;QACb,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;KAC5B,KAAK,IAAI,CAAA;CACX;AAwHD,QAAA,MAAM,MAAM;;;;;;;;CAMX,CAAA;AAED,eAAe,MAAM,CAAA"}
|
package/dist/lint.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
//#region src/lint/index.ts
|
|
2
|
+
/** The raw elements this package replaces, and the primitive that replaces them. */
|
|
3
|
+
var ELEMENT_PRIMITIVES = Object.freeze({
|
|
4
|
+
button: {
|
|
5
|
+
primitive: "Button",
|
|
6
|
+
path: "@adea-ai/ui/components/ui/button"
|
|
7
|
+
},
|
|
8
|
+
input: {
|
|
9
|
+
primitive: "Input",
|
|
10
|
+
path: "@adea-ai/ui/components/ui/input"
|
|
11
|
+
},
|
|
12
|
+
textarea: {
|
|
13
|
+
primitive: "Textarea",
|
|
14
|
+
path: "@adea-ai/ui/components/ui/textarea"
|
|
15
|
+
},
|
|
16
|
+
select: {
|
|
17
|
+
primitive: "Select",
|
|
18
|
+
path: "@adea-ai/ui/components/ui/select"
|
|
19
|
+
},
|
|
20
|
+
option: {
|
|
21
|
+
primitive: "Select",
|
|
22
|
+
path: "@adea-ai/ui/components/ui/select"
|
|
23
|
+
},
|
|
24
|
+
label: {
|
|
25
|
+
primitive: "Label",
|
|
26
|
+
path: "@adea-ai/ui/components/ui/label"
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
var noRawInteractiveElements = {
|
|
30
|
+
meta: {
|
|
31
|
+
type: "problem",
|
|
32
|
+
docs: { description: "Interactive elements this package provides (button, input, textarea, select, option, label) are composed from the package, not written as raw markup." },
|
|
33
|
+
schema: [{
|
|
34
|
+
type: "object",
|
|
35
|
+
properties: {
|
|
36
|
+
allow: {
|
|
37
|
+
type: "array",
|
|
38
|
+
items: { type: "string" }
|
|
39
|
+
},
|
|
40
|
+
message: { type: "string" }
|
|
41
|
+
},
|
|
42
|
+
additionalProperties: false
|
|
43
|
+
}],
|
|
44
|
+
messages: { rawElement: "{{element}} is provided by the design system: use {{primitive}} from {{path}}. A raw element has no keyboard story, no focus behaviour and no token styling — that is the work the primitive already did." }
|
|
45
|
+
},
|
|
46
|
+
create(context) {
|
|
47
|
+
const options = context.options?.[0] ?? {};
|
|
48
|
+
const allowed = new Set(options.allow ?? []);
|
|
49
|
+
const emit = (node, element) => {
|
|
50
|
+
const target = ELEMENT_PRIMITIVES[element];
|
|
51
|
+
context.report({
|
|
52
|
+
node,
|
|
53
|
+
messageId: "rawElement",
|
|
54
|
+
data: {
|
|
55
|
+
element,
|
|
56
|
+
primitive: target.primitive,
|
|
57
|
+
path: target.path
|
|
58
|
+
},
|
|
59
|
+
...options.message ? { message: options.message } : {}
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
return { JSXOpeningElement(node) {
|
|
63
|
+
if (node.name?.type !== "JSXIdentifier") return;
|
|
64
|
+
const element = node.name.name;
|
|
65
|
+
if (!(element in ELEMENT_PRIMITIVES) || allowed.has(element)) return;
|
|
66
|
+
emit(node, element);
|
|
67
|
+
} };
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var DEFAULT_PRIMITIVE_PACKAGES = Object.freeze([
|
|
71
|
+
"@kobalte/core",
|
|
72
|
+
"@ark-ui",
|
|
73
|
+
"@zag-js",
|
|
74
|
+
"@radix-ui",
|
|
75
|
+
"radix-ui",
|
|
76
|
+
"@base-ui-components"
|
|
77
|
+
]);
|
|
78
|
+
var plugin = {
|
|
79
|
+
meta: { name: "adea" },
|
|
80
|
+
rules: {
|
|
81
|
+
"no-raw-interactive-elements": noRawInteractiveElements,
|
|
82
|
+
"no-primitive-library-imports": {
|
|
83
|
+
meta: {
|
|
84
|
+
type: "problem",
|
|
85
|
+
docs: { description: "The primitive libraries the design system is built on (Kobalte and friends) are its internal affair — compose the exported components instead of importing the primitives." },
|
|
86
|
+
schema: [{
|
|
87
|
+
type: "object",
|
|
88
|
+
properties: {
|
|
89
|
+
packages: {
|
|
90
|
+
type: "array",
|
|
91
|
+
items: { type: "string" }
|
|
92
|
+
},
|
|
93
|
+
message: { type: "string" }
|
|
94
|
+
},
|
|
95
|
+
additionalProperties: false
|
|
96
|
+
}],
|
|
97
|
+
messages: { primitiveImport: "Import from the design system, not from {{package}}: the primitives it wraps carry the focus, keyboard and ARIA behaviour the components exist to provide." }
|
|
98
|
+
},
|
|
99
|
+
create(context) {
|
|
100
|
+
const options = context.options?.[0] ?? {};
|
|
101
|
+
const prefixes = options.packages ?? DEFAULT_PRIMITIVE_PACKAGES;
|
|
102
|
+
const offender = (source) => {
|
|
103
|
+
const name = String(source ?? "");
|
|
104
|
+
return prefixes.find((prefix) => name === prefix || name.startsWith(`${prefix}/`)) ?? null;
|
|
105
|
+
};
|
|
106
|
+
const report = (node, source) => {
|
|
107
|
+
const pkg = offender(source);
|
|
108
|
+
context.report({
|
|
109
|
+
node,
|
|
110
|
+
messageId: "primitiveImport",
|
|
111
|
+
data: { package: pkg },
|
|
112
|
+
...options.message ? { message: options.message } : {}
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
return {
|
|
116
|
+
ImportDeclaration(node) {
|
|
117
|
+
if (offender(node.source?.value)) report(node, node.source.value);
|
|
118
|
+
},
|
|
119
|
+
ImportExpression(node) {
|
|
120
|
+
if (typeof node.source?.value === "string" && offender(node.source.value)) report(node, node.source.value);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
//#endregion
|
|
128
|
+
export { plugin as default };
|
|
129
|
+
|
|
130
|
+
//# sourceMappingURL=lint.js.map
|
package/dist/lint.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lint.js","names":[],"sources":["../src/lint/index.ts"],"sourcesContent":["/*\n * The design system's own oxlint plugin.\n *\n * The styling rules live in `@shadcn/lint`; what that plugin cannot know is which\n * *elements* the design system owns. This is the other half of the contract, for\n * the applications that consume the library: an interactive element this package\n * provides is not re-implemented at the application layer, and the primitive\n * libraries this package is built from are not imported around it.\n *\n * Loaded as an oxlint jsPlugin:\n *\n * // .oxlintrc.json\n * { \"jsPlugins\": [\"@adea-ai/ui/lint\"], \"rules\": { \"adea/no-raw-interactive-elements\": \"error\" } }\n *\n * Rules are errors by intent. A violation means an application composed its own\n * control out of raw markup — unreachable focus behaviour, an absent keyboard\n * story, a second implementation of a decision this package exists to make once.\n */\n\ntype Json = string | number | boolean | null | Json[] | { [key: string]: Json }\n\n/** The raw elements this package replaces, and the primitive that replaces them. */\nconst ELEMENT_PRIMITIVES: Readonly<Record<string, { primitive: string; path: string }>> =\n Object.freeze({\n button: { primitive: 'Button', path: '@adea-ai/ui/components/ui/button' },\n input: { primitive: 'Input', path: '@adea-ai/ui/components/ui/input' },\n textarea: { primitive: 'Textarea', path: '@adea-ai/ui/components/ui/textarea' },\n select: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },\n option: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },\n label: { primitive: 'Label', path: '@adea-ai/ui/components/ui/label' },\n })\n\ntype RuleModule = {\n meta: {\n type: 'problem'\n docs: { description: string }\n schema: Json[]\n messages: Record<string, string>\n }\n create: (context: LintContext) => Record<string, (node: any) => void>\n}\n\ninterface LintContext {\n options?: unknown[]\n report: (descriptor: {\n node: unknown\n messageId?: string\n message?: string\n data?: Record<string, Json>\n }) => void\n}\n\ninterface RawElementOptions {\n /** Element names this rule should not report (rare; prefer fixing the file). */\n allow?: string[]\n /** Replaces the default report message. */\n message?: string\n}\n\nconst noRawInteractiveElements: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Interactive elements this package provides (button, input, textarea, select, option, label) are composed from the package, not written as raw markup.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n allow: { type: 'array', items: { type: 'string' } },\n message: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n rawElement:\n '{{element}} is provided by the design system: use {{primitive}} from {{path}}. A raw element has no keyboard story, no focus behaviour and no token styling — that is the work the primitive already did.',\n },\n },\n create(context) {\n const options = (context.options?.[0] ?? {}) as RawElementOptions\n const allowed = new Set(options.allow ?? [])\n const emit = (node: unknown, element: string) => {\n const target = ELEMENT_PRIMITIVES[element]!\n context.report({\n node,\n messageId: 'rawElement',\n data: { element, primitive: target.primitive, path: target.path },\n ...(options.message ? { message: options.message } : {}),\n })\n }\n return {\n JSXOpeningElement(node: any) {\n if (node.name?.type !== 'JSXIdentifier') return\n const element = node.name.name\n if (!(element in ELEMENT_PRIMITIVES) || allowed.has(element)) return\n emit(node, element)\n },\n }\n },\n}\n\ninterface PrimitiveImportOptions {\n /** Package prefixes this rule reports. Defaults to the primitive libraries the ui package wraps. */\n packages?: string[]\n /** Replaces the default report message. */\n message?: string\n}\n\nconst DEFAULT_PRIMITIVE_PACKAGES = Object.freeze([\n '@kobalte/core',\n '@ark-ui',\n '@zag-js',\n '@radix-ui',\n 'radix-ui',\n '@base-ui-components',\n])\n\nconst noPrimitiveLibraryImports: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'The primitive libraries the design system is built on (Kobalte and friends) are its internal affair — compose the exported components instead of importing the primitives.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n packages: { type: 'array', items: { type: 'string' } },\n message: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n primitiveImport:\n 'Import from the design system, not from {{package}}: the primitives it wraps carry the focus, keyboard and ARIA behaviour the components exist to provide.',\n },\n },\n create(context) {\n const options = (context.options?.[0] ?? {}) as PrimitiveImportOptions\n const prefixes = options.packages ?? DEFAULT_PRIMITIVE_PACKAGES\n const offender = (source: unknown): string | null => {\n const name = String(source ?? '')\n return prefixes.find((prefix) => name === prefix || name.startsWith(`${prefix}/`)) ?? null\n }\n const report = (node: unknown, source: unknown) => {\n const pkg = offender(source)!\n context.report({\n node,\n messageId: 'primitiveImport',\n data: { package: pkg },\n ...(options.message ? { message: options.message } : {}),\n })\n }\n return {\n ImportDeclaration(node: any) {\n if (offender(node.source?.value)) report(node, node.source.value)\n },\n ImportExpression(node: any) {\n if (typeof node.source?.value === 'string' && offender(node.source.value))\n report(node, node.source.value)\n },\n }\n },\n}\n\nconst plugin = {\n meta: { name: 'adea' },\n rules: {\n 'no-raw-interactive-elements': noRawInteractiveElements,\n 'no-primitive-library-imports': noPrimitiveLibraryImports,\n },\n}\n\nexport default plugin\n"],"mappings":";;AAsBA,IAAM,qBACJ,OAAO,OAAO;CACZ,QAAQ;EAAE,WAAW;EAAU,MAAM;CAAmC;CACxE,OAAO;EAAE,WAAW;EAAS,MAAM;CAAkC;CACrE,UAAU;EAAE,WAAW;EAAY,MAAM;CAAqC;CAC9E,QAAQ;EAAE,WAAW;EAAU,MAAM;CAAmC;CACxE,QAAQ;EAAE,WAAW;EAAU,MAAM;CAAmC;CACxE,OAAO;EAAE,WAAW;EAAS,MAAM;CAAkC;AACvE,CAAC;AA6BH,IAAM,2BAAuC;CAC3C,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,wJACJ;EACA,QAAQ,CACN;GACE,MAAM;GACN,YAAY;IACV,OAAO;KAAE,MAAM;KAAS,OAAO,EAAE,MAAM,SAAS;IAAE;IAClD,SAAS,EAAE,MAAM,SAAS;GAC5B;GACA,sBAAsB;EACxB,CACF;EACA,UAAU,EACR,YACE,4MACJ;CACF;CACA,OAAO,SAAS;EACd,MAAM,UAAW,QAAQ,UAAU,MAAM,CAAC;EAC1C,MAAM,UAAU,IAAI,IAAI,QAAQ,SAAS,CAAC,CAAC;EAC3C,MAAM,QAAQ,MAAe,YAAoB;GAC/C,MAAM,SAAS,mBAAmB;GAClC,QAAQ,OAAO;IACb;IACA,WAAW;IACX,MAAM;KAAE;KAAS,WAAW,OAAO;KAAW,MAAM,OAAO;IAAK;IAChE,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;GACxD,CAAC;EACH;EACA,OAAO,EACL,kBAAkB,MAAW;GAC3B,IAAI,KAAK,MAAM,SAAS,iBAAiB;GACzC,MAAM,UAAU,KAAK,KAAK;GAC1B,IAAI,EAAE,WAAW,uBAAuB,QAAQ,IAAI,OAAO,GAAG;GAC9D,KAAK,MAAM,OAAO;EACpB,EACF;CACF;AACF;AASA,IAAM,6BAA6B,OAAO,OAAO;CAC/C;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAoDD,IAAM,SAAS;CACb,MAAM,EAAE,MAAM,OAAO;CACrB,OAAO;EACL,+BAA+B;EAC/B,gCAAgC;GArDlC,MAAM;IACJ,MAAM;IACN,MAAM,EACJ,aACE,6KACJ;IACA,QAAQ,CACN;KACE,MAAM;KACN,YAAY;MACV,UAAU;OAAE,MAAM;OAAS,OAAO,EAAE,MAAM,SAAS;MAAE;MACrD,SAAS,EAAE,MAAM,SAAS;KAC5B;KACA,sBAAsB;IACxB,CACF;IACA,UAAU,EACR,iBACE,6JACJ;GACF;GACA,OAAO,SAAS;IACd,MAAM,UAAW,QAAQ,UAAU,MAAM,CAAC;IAC1C,MAAM,WAAW,QAAQ,YAAY;IACrC,MAAM,YAAY,WAAmC;KACnD,MAAM,OAAO,OAAO,UAAU,EAAE;KAChC,OAAO,SAAS,MAAM,WAAW,SAAS,UAAU,KAAK,WAAW,GAAG,OAAO,EAAE,CAAC,KAAK;IACxF;IACA,MAAM,UAAU,MAAe,WAAoB;KACjD,MAAM,MAAM,SAAS,MAAM;KAC3B,QAAQ,OAAO;MACb;MACA,WAAW;MACX,MAAM,EAAE,SAAS,IAAI;MACrB,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;KACxD,CAAC;IACH;IACA,OAAO;KACL,kBAAkB,MAAW;MAC3B,IAAI,SAAS,KAAK,QAAQ,KAAK,GAAG,OAAO,MAAM,KAAK,OAAO,KAAK;KAClE;KACA,iBAAiB,MAAW;MAC1B,IAAI,OAAO,KAAK,QAAQ,UAAU,YAAY,SAAS,KAAK,OAAO,KAAK,GACtE,OAAO,MAAM,KAAK,OAAO,KAAK;KAClC;IACF;GACF;EAOkC;CAClC;AACF"}
|
|
@@ -90,7 +90,7 @@ export function StatusChip(props: StatusChipProps) {
|
|
|
90
90
|
<TooltipTrigger as="span" class="inline-flex">
|
|
91
91
|
{chip}
|
|
92
92
|
</TooltipTrigger>
|
|
93
|
-
<TooltipContent>{detail()}</TooltipContent>
|
|
93
|
+
<TooltipContent hideArrow>{detail()}</TooltipContent>
|
|
94
94
|
</Tooltip>
|
|
95
95
|
)}
|
|
96
96
|
</Show>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adea-ai/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.78.1",
|
|
4
4
|
"description": "The Adea design system: Kobalte-backed Solid components, semantic OKLCH tokens, and the app-shell layout primitives shared by Adea and Cortana.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"adea",
|
|
@@ -59,6 +59,10 @@
|
|
|
59
59
|
"development": "./src/lib/*.ts",
|
|
60
60
|
"import": "./dist/lib/*.js"
|
|
61
61
|
},
|
|
62
|
+
"./lint": {
|
|
63
|
+
"types": "./dist/lint/index.d.ts",
|
|
64
|
+
"default": "./dist/lint.js"
|
|
65
|
+
},
|
|
62
66
|
"./globals.css": "./src/styles/globals.css",
|
|
63
67
|
"./theme.css": "./src/styles/theme.css",
|
|
64
68
|
"./base.css": "./src/styles/base.css",
|
|
@@ -90,7 +90,7 @@ export function StatusChip(props: StatusChipProps) {
|
|
|
90
90
|
<TooltipTrigger as="span" class="inline-flex">
|
|
91
91
|
{chip}
|
|
92
92
|
</TooltipTrigger>
|
|
93
|
-
<TooltipContent>{detail()}</TooltipContent>
|
|
93
|
+
<TooltipContent hideArrow>{detail()}</TooltipContent>
|
|
94
94
|
</Tooltip>
|
|
95
95
|
)}
|
|
96
96
|
</Show>
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The design system's own oxlint plugin.
|
|
3
|
+
*
|
|
4
|
+
* The styling rules live in `@shadcn/lint`; what that plugin cannot know is which
|
|
5
|
+
* *elements* the design system owns. This is the other half of the contract, for
|
|
6
|
+
* the applications that consume the library: an interactive element this package
|
|
7
|
+
* provides is not re-implemented at the application layer, and the primitive
|
|
8
|
+
* libraries this package is built from are not imported around it.
|
|
9
|
+
*
|
|
10
|
+
* Loaded as an oxlint jsPlugin:
|
|
11
|
+
*
|
|
12
|
+
* // .oxlintrc.json
|
|
13
|
+
* { "jsPlugins": ["@adea-ai/ui/lint"], "rules": { "adea/no-raw-interactive-elements": "error" } }
|
|
14
|
+
*
|
|
15
|
+
* Rules are errors by intent. A violation means an application composed its own
|
|
16
|
+
* control out of raw markup — unreachable focus behaviour, an absent keyboard
|
|
17
|
+
* story, a second implementation of a decision this package exists to make once.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
type Json = string | number | boolean | null | Json[] | { [key: string]: Json }
|
|
21
|
+
|
|
22
|
+
/** The raw elements this package replaces, and the primitive that replaces them. */
|
|
23
|
+
const ELEMENT_PRIMITIVES: Readonly<Record<string, { primitive: string; path: string }>> =
|
|
24
|
+
Object.freeze({
|
|
25
|
+
button: { primitive: 'Button', path: '@adea-ai/ui/components/ui/button' },
|
|
26
|
+
input: { primitive: 'Input', path: '@adea-ai/ui/components/ui/input' },
|
|
27
|
+
textarea: { primitive: 'Textarea', path: '@adea-ai/ui/components/ui/textarea' },
|
|
28
|
+
select: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },
|
|
29
|
+
option: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },
|
|
30
|
+
label: { primitive: 'Label', path: '@adea-ai/ui/components/ui/label' },
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
type RuleModule = {
|
|
34
|
+
meta: {
|
|
35
|
+
type: 'problem'
|
|
36
|
+
docs: { description: string }
|
|
37
|
+
schema: Json[]
|
|
38
|
+
messages: Record<string, string>
|
|
39
|
+
}
|
|
40
|
+
create: (context: LintContext) => Record<string, (node: any) => void>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface LintContext {
|
|
44
|
+
options?: unknown[]
|
|
45
|
+
report: (descriptor: {
|
|
46
|
+
node: unknown
|
|
47
|
+
messageId?: string
|
|
48
|
+
message?: string
|
|
49
|
+
data?: Record<string, Json>
|
|
50
|
+
}) => void
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface RawElementOptions {
|
|
54
|
+
/** Element names this rule should not report (rare; prefer fixing the file). */
|
|
55
|
+
allow?: string[]
|
|
56
|
+
/** Replaces the default report message. */
|
|
57
|
+
message?: string
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const noRawInteractiveElements: RuleModule = {
|
|
61
|
+
meta: {
|
|
62
|
+
type: 'problem',
|
|
63
|
+
docs: {
|
|
64
|
+
description:
|
|
65
|
+
'Interactive elements this package provides (button, input, textarea, select, option, label) are composed from the package, not written as raw markup.',
|
|
66
|
+
},
|
|
67
|
+
schema: [
|
|
68
|
+
{
|
|
69
|
+
type: 'object',
|
|
70
|
+
properties: {
|
|
71
|
+
allow: { type: 'array', items: { type: 'string' } },
|
|
72
|
+
message: { type: 'string' },
|
|
73
|
+
},
|
|
74
|
+
additionalProperties: false,
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
messages: {
|
|
78
|
+
rawElement:
|
|
79
|
+
'{{element}} is provided by the design system: use {{primitive}} from {{path}}. A raw element has no keyboard story, no focus behaviour and no token styling — that is the work the primitive already did.',
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
create(context) {
|
|
83
|
+
const options = (context.options?.[0] ?? {}) as RawElementOptions
|
|
84
|
+
const allowed = new Set(options.allow ?? [])
|
|
85
|
+
const emit = (node: unknown, element: string) => {
|
|
86
|
+
const target = ELEMENT_PRIMITIVES[element]!
|
|
87
|
+
context.report({
|
|
88
|
+
node,
|
|
89
|
+
messageId: 'rawElement',
|
|
90
|
+
data: { element, primitive: target.primitive, path: target.path },
|
|
91
|
+
...(options.message ? { message: options.message } : {}),
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
JSXOpeningElement(node: any) {
|
|
96
|
+
if (node.name?.type !== 'JSXIdentifier') return
|
|
97
|
+
const element = node.name.name
|
|
98
|
+
if (!(element in ELEMENT_PRIMITIVES) || allowed.has(element)) return
|
|
99
|
+
emit(node, element)
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
interface PrimitiveImportOptions {
|
|
106
|
+
/** Package prefixes this rule reports. Defaults to the primitive libraries the ui package wraps. */
|
|
107
|
+
packages?: string[]
|
|
108
|
+
/** Replaces the default report message. */
|
|
109
|
+
message?: string
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const DEFAULT_PRIMITIVE_PACKAGES = Object.freeze([
|
|
113
|
+
'@kobalte/core',
|
|
114
|
+
'@ark-ui',
|
|
115
|
+
'@zag-js',
|
|
116
|
+
'@radix-ui',
|
|
117
|
+
'radix-ui',
|
|
118
|
+
'@base-ui-components',
|
|
119
|
+
])
|
|
120
|
+
|
|
121
|
+
const noPrimitiveLibraryImports: RuleModule = {
|
|
122
|
+
meta: {
|
|
123
|
+
type: 'problem',
|
|
124
|
+
docs: {
|
|
125
|
+
description:
|
|
126
|
+
'The primitive libraries the design system is built on (Kobalte and friends) are its internal affair — compose the exported components instead of importing the primitives.',
|
|
127
|
+
},
|
|
128
|
+
schema: [
|
|
129
|
+
{
|
|
130
|
+
type: 'object',
|
|
131
|
+
properties: {
|
|
132
|
+
packages: { type: 'array', items: { type: 'string' } },
|
|
133
|
+
message: { type: 'string' },
|
|
134
|
+
},
|
|
135
|
+
additionalProperties: false,
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
messages: {
|
|
139
|
+
primitiveImport:
|
|
140
|
+
'Import from the design system, not from {{package}}: the primitives it wraps carry the focus, keyboard and ARIA behaviour the components exist to provide.',
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
create(context) {
|
|
144
|
+
const options = (context.options?.[0] ?? {}) as PrimitiveImportOptions
|
|
145
|
+
const prefixes = options.packages ?? DEFAULT_PRIMITIVE_PACKAGES
|
|
146
|
+
const offender = (source: unknown): string | null => {
|
|
147
|
+
const name = String(source ?? '')
|
|
148
|
+
return prefixes.find((prefix) => name === prefix || name.startsWith(`${prefix}/`)) ?? null
|
|
149
|
+
}
|
|
150
|
+
const report = (node: unknown, source: unknown) => {
|
|
151
|
+
const pkg = offender(source)!
|
|
152
|
+
context.report({
|
|
153
|
+
node,
|
|
154
|
+
messageId: 'primitiveImport',
|
|
155
|
+
data: { package: pkg },
|
|
156
|
+
...(options.message ? { message: options.message } : {}),
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
ImportDeclaration(node: any) {
|
|
161
|
+
if (offender(node.source?.value)) report(node, node.source.value)
|
|
162
|
+
},
|
|
163
|
+
ImportExpression(node: any) {
|
|
164
|
+
if (typeof node.source?.value === 'string' && offender(node.source.value))
|
|
165
|
+
report(node, node.source.value)
|
|
166
|
+
},
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const plugin = {
|
|
172
|
+
meta: { name: 'adea' },
|
|
173
|
+
rules: {
|
|
174
|
+
'no-raw-interactive-elements': noRawInteractiveElements,
|
|
175
|
+
'no-primitive-library-imports': noPrimitiveLibraryImports,
|
|
176
|
+
},
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export default plugin
|