@flanksource/clicky-ui 0.3.6 → 0.3.8
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/README.md +42 -0
- package/dist/components/DateField.d.ts +1 -1
- package/dist/components/DatePicker.d.ts +1 -1
- package/dist/components/DateTimePicker.d.ts +1 -1
- package/dist/components/FormLookupProvider.cjs +12 -0
- package/dist/components/FormLookupProvider.cjs.map +1 -0
- package/dist/components/FormLookupProvider.d.ts +7 -0
- package/dist/components/FormLookupProvider.d.ts.map +1 -0
- package/dist/components/FormLookupProvider.js +12 -0
- package/dist/components/FormLookupProvider.js.map +1 -0
- package/dist/components/JsonSchemaForm.cjs +2 -2
- package/dist/components/JsonSchemaForm.cjs.map +1 -1
- package/dist/components/JsonSchemaForm.js +1 -1
- package/dist/components/JsonSchemaForm.js.map +1 -1
- package/dist/components/ListMenu.cjs +145 -0
- package/dist/components/ListMenu.cjs.map +1 -0
- package/dist/components/ListMenu.d.ts +54 -0
- package/dist/components/ListMenu.d.ts.map +1 -0
- package/dist/components/ListMenu.js +145 -0
- package/dist/components/ListMenu.js.map +1 -0
- package/dist/components/MdxEditorField.cjs +128 -0
- package/dist/components/MdxEditorField.cjs.map +1 -0
- package/dist/components/MdxEditorField.d.ts +4 -0
- package/dist/components/MdxEditorField.d.ts.map +1 -0
- package/dist/components/MdxEditorField.js +106 -0
- package/dist/components/MdxEditorField.js.map +1 -0
- package/dist/components/MdxEditorToolbar.cjs +76 -0
- package/dist/components/MdxEditorToolbar.cjs.map +1 -0
- package/dist/components/MdxEditorToolbar.d.ts +8 -0
- package/dist/components/MdxEditorToolbar.d.ts.map +1 -0
- package/dist/components/MdxEditorToolbar.js +76 -0
- package/dist/components/MdxEditorToolbar.js.map +1 -0
- package/dist/components/button-variants.d.ts +1 -1
- package/dist/components/button.d.ts +1 -1
- package/dist/components/form-lookup-context.cjs +1 -8
- package/dist/components/form-lookup-context.cjs.map +1 -1
- package/dist/components/form-lookup-context.d.ts +1 -5
- package/dist/components/form-lookup-context.d.ts.map +1 -1
- package/dist/components/form-lookup-context.js +1 -8
- package/dist/components/form-lookup-context.js.map +1 -1
- package/dist/components/jotai-bindings-core.cjs +197 -0
- package/dist/components/jotai-bindings-core.cjs.map +1 -0
- package/dist/components/jotai-bindings-core.d.ts +45 -0
- package/dist/components/jotai-bindings-core.d.ts.map +1 -0
- package/dist/components/jotai-bindings-core.js +197 -0
- package/dist/components/jotai-bindings-core.js.map +1 -0
- package/dist/components/jotai-bindings.cjs +17 -0
- package/dist/components/jotai-bindings.cjs.map +1 -0
- package/dist/components/jotai-bindings.d.ts +4 -0
- package/dist/components/jotai-bindings.d.ts.map +1 -0
- package/dist/components/jotai-bindings.js +17 -0
- package/dist/components/jotai-bindings.js.map +1 -0
- package/dist/components/json-schema-form-fields.cjs +37 -0
- package/dist/components/json-schema-form-fields.cjs.map +1 -1
- package/dist/components/json-schema-form-fields.d.ts +6 -0
- package/dist/components/json-schema-form-fields.d.ts.map +1 -1
- package/dist/components/json-schema-form-fields.js +38 -1
- package/dist/components/json-schema-form-fields.js.map +1 -1
- package/dist/components/json-schema-form-render.cjs +3 -1
- package/dist/components/json-schema-form-render.cjs.map +1 -1
- package/dist/components/json-schema-form-render.d.ts.map +1 -1
- package/dist/components/json-schema-form-render.js +4 -2
- package/dist/components/json-schema-form-render.js.map +1 -1
- package/dist/components/json-schema-form-resolve.cjs +9 -0
- package/dist/components/json-schema-form-resolve.cjs.map +1 -1
- package/dist/components/json-schema-form-resolve.d.ts.map +1 -1
- package/dist/components/json-schema-form-resolve.js +9 -0
- package/dist/components/json-schema-form-resolve.js.map +1 -1
- package/dist/components/json-schema-form-types.d.ts +4 -1
- package/dist/components/json-schema-form-types.d.ts.map +1 -1
- package/dist/components/mdx-editor-field-plugins.cjs +124 -0
- package/dist/components/mdx-editor-field-plugins.cjs.map +1 -0
- package/dist/components/mdx-editor-field-plugins.d.ts +6 -0
- package/dist/components/mdx-editor-field-plugins.d.ts.map +1 -0
- package/dist/components/mdx-editor-field-plugins.js +124 -0
- package/dist/components/mdx-editor-field-plugins.js.map +1 -0
- package/dist/components/mdx-editor-options.d.ts +77 -0
- package/dist/components/mdx-editor-options.d.ts.map +1 -0
- package/dist/components/use-list-menu-selection.cjs +77 -0
- package/dist/components/use-list-menu-selection.cjs.map +1 -0
- package/dist/components/use-list-menu-selection.d.ts +40 -0
- package/dist/components/use-list-menu-selection.d.ts.map +1 -0
- package/dist/components/use-list-menu-selection.js +77 -0
- package/dist/components/use-list-menu-selection.js.map +1 -0
- package/dist/components.cjs +10 -1
- package/dist/components.cjs.map +1 -1
- package/dist/components.d.ts +4 -1
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +10 -1
- package/dist/components.js.map +1 -1
- package/dist/data/MarkdownEditor.model.d.ts +1 -1
- package/dist/data/PromptBanner.cjs +199 -0
- package/dist/data/PromptBanner.cjs.map +1 -0
- package/dist/data/PromptBanner.d.ts +17 -0
- package/dist/data/PromptBanner.d.ts.map +1 -0
- package/dist/data/PromptBanner.js +199 -0
- package/dist/data/PromptBanner.js.map +1 -0
- package/dist/data/PromptBanner.model.cjs +132 -0
- package/dist/data/PromptBanner.model.cjs.map +1 -0
- package/dist/data/PromptBanner.model.d.ts +23 -0
- package/dist/data/PromptBanner.model.d.ts.map +1 -0
- package/dist/data/PromptBanner.model.js +132 -0
- package/dist/data/PromptBanner.model.js.map +1 -0
- package/dist/data/PromptDialog.cjs +43 -0
- package/dist/data/PromptDialog.cjs.map +1 -0
- package/dist/data/PromptDialog.d.ts +14 -0
- package/dist/data/PromptDialog.d.ts.map +1 -0
- package/dist/data/PromptDialog.js +43 -0
- package/dist/data/PromptDialog.js.map +1 -0
- package/dist/data/TaskManager.cjs +8 -1
- package/dist/data/TaskManager.cjs.map +1 -1
- package/dist/data/TaskManager.d.ts.map +1 -1
- package/dist/data/TaskManager.js +8 -1
- package/dist/data/TaskManager.js.map +1 -1
- package/dist/data/TaskProgress.cjs +15 -2
- package/dist/data/TaskProgress.cjs.map +1 -1
- package/dist/data/TaskProgress.d.ts +0 -10
- package/dist/data/TaskProgress.d.ts.map +1 -1
- package/dist/data/TaskProgress.js +15 -2
- package/dist/data/TaskProgress.js.map +1 -1
- package/dist/data/ai/ChatWindow.cjs +53 -9
- package/dist/data/ai/ChatWindow.cjs.map +1 -1
- package/dist/data/ai/ChatWindow.d.ts.map +1 -1
- package/dist/data/ai/ChatWindow.js +53 -9
- package/dist/data/ai/ChatWindow.js.map +1 -1
- package/dist/data/ai/ToolPreferences.cjs +232 -39
- package/dist/data/ai/ToolPreferences.cjs.map +1 -1
- package/dist/data/ai/ToolPreferences.d.ts +4 -7
- package/dist/data/ai/ToolPreferences.d.ts.map +1 -1
- package/dist/data/ai/ToolPreferences.js +234 -41
- package/dist/data/ai/ToolPreferences.js.map +1 -1
- package/dist/data/chat/clickyOperationsToTools.cjs +89 -3
- package/dist/data/chat/clickyOperationsToTools.cjs.map +1 -1
- package/dist/data/chat/clickyOperationsToTools.d.ts +1 -1
- package/dist/data/chat/clickyOperationsToTools.d.ts.map +1 -1
- package/dist/data/chat/clickyOperationsToTools.js +89 -3
- package/dist/data/chat/clickyOperationsToTools.js.map +1 -1
- package/dist/data/chat/types.cjs.map +1 -1
- package/dist/data/chat/types.d.ts +7 -1
- package/dist/data/chat/types.d.ts.map +1 -1
- package/dist/data/chat/types.js.map +1 -1
- package/dist/data/task-status.cjs +19 -12
- package/dist/data/task-status.cjs.map +1 -1
- package/dist/data/task-status.d.ts +2 -1
- package/dist/data/task-status.d.ts.map +1 -1
- package/dist/data/task-status.js +19 -12
- package/dist/data/task-status.js.map +1 -1
- package/dist/data/test-runner/TestRunner.fixtures.d.ts +11 -4
- package/dist/data/test-runner/TestRunner.fixtures.d.ts.map +1 -1
- package/dist/data/version-info.cjs +3 -3
- package/dist/data/version-info.js +3 -3
- package/dist/data.cjs +7 -0
- package/dist/data.cjs.map +1 -1
- package/dist/data.d.ts +3 -0
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +7 -0
- package/dist/data.js.map +1 -1
- package/dist/hooks/use-prompts.cjs +83 -0
- package/dist/hooks/use-prompts.cjs.map +1 -0
- package/dist/hooks/use-prompts.d.ts +43 -0
- package/dist/hooks/use-prompts.d.ts.map +1 -0
- package/dist/hooks/use-prompts.js +83 -0
- package/dist/hooks/use-prompts.js.map +1 -0
- package/dist/hooks.cjs +3 -0
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.d.ts +1 -0
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +3 -0
- package/dist/hooks.js.map +1 -1
- package/dist/index.cjs +17 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +17 -1
- package/dist/index.js.map +1 -1
- package/dist/jotai.cjs +9 -0
- package/dist/jotai.cjs.map +1 -0
- package/dist/jotai.d.ts +4 -0
- package/dist/jotai.d.ts.map +1 -0
- package/dist/jotai.js +9 -0
- package/dist/jotai.js.map +1 -0
- package/dist/lib/utils.cjs +18 -1
- package/dist/lib/utils.cjs.map +1 -1
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +18 -1
- package/dist/lib/utils.js.map +1 -1
- package/dist/mdx-editor.cjs +5 -0
- package/dist/mdx-editor.cjs.map +1 -0
- package/dist/mdx-editor.css +2 -0
- package/dist/mdx-editor.d.ts +2 -0
- package/dist/mdx-editor.d.ts.map +1 -0
- package/dist/mdx-editor.js +5 -0
- package/dist/mdx-editor.js.map +1 -0
- package/dist/rpc/apiClient.cjs +1 -1
- package/dist/rpc/apiClient.cjs.map +1 -1
- package/dist/rpc/apiClient.js +1 -1
- package/dist/rpc/apiClient.js.map +1 -1
- package/dist/rpc/surfaceIconMap.cjs +12 -1
- package/dist/rpc/surfaceIconMap.cjs.map +1 -1
- package/dist/rpc/surfaceIconMap.d.ts.map +1 -1
- package/dist/rpc/surfaceIconMap.js +12 -1
- package/dist/rpc/surfaceIconMap.js.map +1 -1
- package/dist/rpc/types.cjs.map +1 -1
- package/dist/rpc/types.d.ts +1 -0
- package/dist/rpc/types.d.ts.map +1 -1
- package/dist/rpc/types.js.map +1 -1
- package/dist/styles.css +1 -1
- package/oxlint-plugins/README.md +148 -0
- package/oxlint-plugins/clicky-ui-no-adhoc-overlay.js +88 -0
- package/oxlint-plugins/clicky-ui-prefer-components.js +46 -0
- package/oxlint-plugins/clicky-ui-prefer-icons.js +121 -0
- package/oxlint-plugins/clicky-ui-prefer-tailwind.js +98 -0
- package/oxlint-plugins/clicky-ui-prefer-theme.js +134 -0
- package/oxlint-plugins/clicky-ui-shared.js +132 -0
- package/oxlint-plugins/clicky-ui.js +34 -0
- package/oxlint-plugins/no-iconify-names.js +37 -0
- package/package.json +24 -4
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# clicky-ui lint rules
|
|
2
|
+
|
|
3
|
+
Custom [oxlint](https://oxc.rs/docs/guide/usage/linter/js-plugins.html) rules that
|
|
4
|
+
projects **consuming** `@flanksource/clicky-ui` can enable to keep their code on
|
|
5
|
+
the library's components, overlays, theming and icons instead of re-implementing
|
|
6
|
+
them by hand.
|
|
7
|
+
|
|
8
|
+
The rules ship as a JS plugin inside the published package
|
|
9
|
+
(`@flanksource/clicky-ui/oxlint-plugins`), so a downstream repo can turn them on
|
|
10
|
+
without copying anything.
|
|
11
|
+
|
|
12
|
+
## Setup
|
|
13
|
+
|
|
14
|
+
These rules require oxlint's [JS plugins](https://oxc.rs/docs/guide/usage/linter/js-plugins.html)
|
|
15
|
+
(oxlint `>=1.50`, Node `>=20.19`). Add the plugin and the rules you want to your
|
|
16
|
+
downstream `.oxlintrc.json`:
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"jsPlugins": [
|
|
21
|
+
{ "name": "clicky-ui", "specifier": "@flanksource/clicky-ui/oxlint-plugins" }
|
|
22
|
+
],
|
|
23
|
+
"rules": {
|
|
24
|
+
"clicky-ui/prefer-clicky-components": "warn",
|
|
25
|
+
"clicky-ui/no-adhoc-overlay": "error",
|
|
26
|
+
"clicky-ui/prefer-tailwind-classes": "warn",
|
|
27
|
+
"clicky-ui/prefer-theme-tokens": "error",
|
|
28
|
+
"clicky-ui/prefer-clicky-icons": "warn"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The `specifier` form resolves through the package's `exports` map. If your
|
|
34
|
+
toolchain can't resolve it, point `jsPlugins` at the file directly instead:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"jsPlugins": ["./node_modules/@flanksource/clicky-ui/oxlint-plugins/clicky-ui.js"]
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Every rule is **opt-in** — nothing is enabled until you list it. Choose the
|
|
43
|
+
severity (`"warn"` / `"error"`) per rule.
|
|
44
|
+
|
|
45
|
+
## Rules
|
|
46
|
+
|
|
47
|
+
| Rule | Flags | Use instead |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| `clicky-ui/prefer-clicky-components` | Native `<button>`, `<select>`, `<table>`, `<dialog>` that re-implement a shipped component | `Button`/`IconButton`, `Select`/`Combobox`, `DataTable`, `Modal` |
|
|
50
|
+
| `clicky-ui/no-adhoc-overlay` | `role="dialog"`/`"alertdialog"`, arbitrary `z-[N]` classes, `style={{ zIndex: N }}` | `Modal`, `DropdownMenu`, `Toast`, and the `zIndex` scale |
|
|
51
|
+
| `clicky-ui/prefer-tailwind-classes` | Inline `style` with **static** spacing/layout/typography values (`padding`, `margin`, `gap`, `display`, `fontWeight`, …) | Tailwind utilities + density utilities (`p-density-4`, `gap-density-2`) |
|
|
52
|
+
| `clicky-ui/prefer-theme-tokens` | Hardcoded colors (`text-[#fff]`, `style={{ color: "#fff" }}`) and hand-reading the `clicky-ui-theme`/`clicky-ui-density` storage keys | Semantic tokens (`text-foreground`, `bg-background`, …) and the `useTheme`/`useDensity` hooks |
|
|
53
|
+
| `clicky-ui/prefer-clicky-icons` | Icon imports from third-party libraries (lucide, react-icons, heroicons, MUI, phosphor, tabler, font-awesome, …), emoji used as icons, and raw iconify name strings (`codicon:clock`, `lucide:activity`, …) passed as a value/argument | `@flanksource/clicky-ui/icons` (`Ui*`), `@flanksource/icons/mi`, or `@iconify/react` |
|
|
54
|
+
|
|
55
|
+
### `prefer-clicky-components`
|
|
56
|
+
|
|
57
|
+
Rebuilding a primitive by hand means re-doing its styling, theming, density and
|
|
58
|
+
accessibility — and drifting from the library over time. Reuse the component.
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
// ✗ flagged
|
|
62
|
+
<button onClick={save}>Save</button>;
|
|
63
|
+
|
|
64
|
+
// ✓
|
|
65
|
+
import { Button } from "@flanksource/clicky-ui/components";
|
|
66
|
+
<Button onClick={save}>Save</Button>;
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### `no-adhoc-overlay`
|
|
70
|
+
|
|
71
|
+
Overlays must share clicky-ui's centralized z-index scale (`overlay/zIndex.ts`)
|
|
72
|
+
so a dropdown opened inside a modal can't render behind it. Arbitrary `z-[N]`
|
|
73
|
+
Tailwind classes are doubly bad: a consumer's Tailwind build only emits the
|
|
74
|
+
arbitrary classes it can scan from the published dist, so a fresh `z-[N]` may
|
|
75
|
+
compile to nothing.
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
// ✗ flagged
|
|
79
|
+
<div role="dialog" className="z-[9999]">…</div>;
|
|
80
|
+
|
|
81
|
+
// ✓
|
|
82
|
+
import { Modal } from "@flanksource/clicky-ui/components";
|
|
83
|
+
<Modal open={open} onClose={close}>…</Modal>;
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### `prefer-tailwind-classes`
|
|
87
|
+
|
|
88
|
+
Static inline styles bypass Tailwind and the density system. **Computed** values
|
|
89
|
+
(`var()`, `calc()`, `clamp()`, identifiers, expressions) are intentionally
|
|
90
|
+
allowed — those legitimately can't be a static class.
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
// ✗ flagged
|
|
94
|
+
<div style={{ padding: 8, gap: 4 }} />;
|
|
95
|
+
|
|
96
|
+
// ✓
|
|
97
|
+
<div className="p-density-4 gap-density-2" />;
|
|
98
|
+
|
|
99
|
+
// ✓ allowed — dynamic value can't be a static class
|
|
100
|
+
<div style={{ fontSize: "clamp(1rem, 18cqmin, 2.5rem)" }} />;
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### `prefer-theme-tokens`
|
|
104
|
+
|
|
105
|
+
Hardcoded colors don't flip with `[data-theme]`, defeating light/dark theming.
|
|
106
|
+
Reading the storage keys by hand re-implements the hooks.
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
// ✗ flagged
|
|
110
|
+
<span className="text-[#111]" style={{ backgroundColor: "#fff" }} />;
|
|
111
|
+
localStorage.getItem("clicky-ui-theme");
|
|
112
|
+
|
|
113
|
+
// ✓
|
|
114
|
+
<span className="text-foreground bg-background" />;
|
|
115
|
+
import { useTheme } from "@flanksource/clicky-ui/hooks";
|
|
116
|
+
const { theme } = useTheme();
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### `prefer-clicky-icons`
|
|
120
|
+
|
|
121
|
+
Icons should come from an approved source and be real components, not emoji,
|
|
122
|
+
glyphs pulled from an unrelated icon library, or raw iconify name strings
|
|
123
|
+
(known icon-set prefixes: `ph`, `lucide`, `codicon`, `tabler`, `mdi`,
|
|
124
|
+
`svg-spinners`, `jb-expui-*`) passed as a value or argument.
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
// ✗ flagged
|
|
128
|
+
import { Star } from "lucide-react";
|
|
129
|
+
<span>🔍</span>;
|
|
130
|
+
const icon = "codicon:clock"; // raw iconify name string
|
|
131
|
+
<Icon icon="lucide:activity" />;
|
|
132
|
+
|
|
133
|
+
// ✓
|
|
134
|
+
import { UiStar } from "@flanksource/clicky-ui/icons";
|
|
135
|
+
import { K8SSecret } from "@flanksource/icons/mi";
|
|
136
|
+
import { Icon } from "@iconify/react";
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Developing the rules
|
|
140
|
+
|
|
141
|
+
Each rule lives in its own `clicky-ui-*.js` file and exports a pure predicate
|
|
142
|
+
(e.g. `isArbitraryZIndexClass`, `isUnapprovedIconImport`) that is unit-tested in
|
|
143
|
+
`clicky-ui.test.ts`. `clicky-ui.js` is the plugin entry that registers them. Run
|
|
144
|
+
the tests with:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
pnpm --filter @flanksource/clicky-ui test clicky-ui
|
|
148
|
+
```
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// clicky-ui/no-adhoc-overlay — keep overlays going through clicky-ui's Modal and
|
|
2
|
+
// its centralized z-index scale (overlay/zIndex.ts) instead of hand-rolled
|
|
3
|
+
// dialogs and arbitrary stacking values that silently fight the modal stack.
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
attributeName,
|
|
7
|
+
classTokens,
|
|
8
|
+
collectStrings,
|
|
9
|
+
numericLiteral,
|
|
10
|
+
propertyKey,
|
|
11
|
+
styleObject,
|
|
12
|
+
} from "./clicky-ui-shared.js";
|
|
13
|
+
|
|
14
|
+
// `z-[999]`, `hover:z-[999]`, `md:z-[var(--x)]` — an arbitrary Tailwind z-index.
|
|
15
|
+
// Named utilities (`z-50`) are fine; only escape-hatch arbitrary values are flagged.
|
|
16
|
+
const ARBITRARY_Z_CLASS = /(?:^|:)z-\[[^\]]+\]$/;
|
|
17
|
+
const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
|
|
18
|
+
|
|
19
|
+
/** True for an arbitrary `z-[…]` Tailwind class token (variant prefixes allowed). */
|
|
20
|
+
export function isArbitraryZIndexClass(token) {
|
|
21
|
+
return typeof token === "string" && ARBITRARY_Z_CLASS.test(token);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** True for a `role` value that turns a plain element into an ad-hoc dialog. */
|
|
25
|
+
export function isDialogRole(value) {
|
|
26
|
+
return typeof value === "string" && DIALOG_ROLES.has(value);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const Z_INDEX_MESSAGE =
|
|
30
|
+
"Hardcoded z-index. Use clicky-ui's overlay components (Modal, DropdownMenu, " +
|
|
31
|
+
"Toast) or the `zIndex` scale from @flanksource/clicky-ui so stacking stays " +
|
|
32
|
+
"consistent — arbitrary z-[N] classes can also be dropped by a consumer's " +
|
|
33
|
+
"Tailwind build.";
|
|
34
|
+
|
|
35
|
+
function checkClassName(node, context) {
|
|
36
|
+
if (attributeName(node) !== "className" && attributeName(node) !== "class") return;
|
|
37
|
+
for (const token of classTokens(collectStrings(node.value))) {
|
|
38
|
+
if (isArbitraryZIndexClass(token)) {
|
|
39
|
+
context.report({ message: Z_INDEX_MESSAGE, node });
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function checkStyleZIndex(node, context) {
|
|
46
|
+
if (attributeName(node) !== "style") return;
|
|
47
|
+
const object = styleObject(node);
|
|
48
|
+
if (!object) return;
|
|
49
|
+
for (const prop of object.properties) {
|
|
50
|
+
if (propertyKey(prop) === "zIndex" && numericLiteral(prop.value) !== undefined) {
|
|
51
|
+
context.report({ message: Z_INDEX_MESSAGE, node: prop });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function checkDialogRole(node, context) {
|
|
57
|
+
if (attributeName(node) !== "role") return;
|
|
58
|
+
const value = node.value?.type === "Literal" ? node.value.value : undefined;
|
|
59
|
+
if (isDialogRole(value)) {
|
|
60
|
+
context.report({
|
|
61
|
+
message:
|
|
62
|
+
`role="${value}" builds an ad-hoc dialog. Use clicky-ui's Modal, which ` +
|
|
63
|
+
"owns focus trapping and the modal z-index stack.",
|
|
64
|
+
node,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const rule = {
|
|
70
|
+
meta: {
|
|
71
|
+
type: "problem",
|
|
72
|
+
docs: {
|
|
73
|
+
description:
|
|
74
|
+
"Use clicky-ui overlays and the centralized z-index scale instead of ad-hoc dialogs",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
create(context) {
|
|
78
|
+
return {
|
|
79
|
+
JSXAttribute(node) {
|
|
80
|
+
checkClassName(node, context);
|
|
81
|
+
checkStyleZIndex(node, context);
|
|
82
|
+
checkDialogRole(node, context);
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export default rule;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// clicky-ui/prefer-clicky-components — flag native DOM elements that re-implement
|
|
2
|
+
// a component clicky-ui already ships, so consumers reuse the library instead of
|
|
3
|
+
// rebuilding (and re-styling, re-theming) primitives by hand.
|
|
4
|
+
|
|
5
|
+
import { intrinsicElementName } from "./clicky-ui-shared.js";
|
|
6
|
+
|
|
7
|
+
// Intrinsic element → the clicky-ui component(s) that should replace it.
|
|
8
|
+
const REPLACEMENTS = {
|
|
9
|
+
button: "Button or IconButton (@flanksource/clicky-ui/components)",
|
|
10
|
+
select: "Select or Combobox (@flanksource/clicky-ui/components)",
|
|
11
|
+
table: "DataTable (@flanksource/clicky-ui/data)",
|
|
12
|
+
dialog: "Modal (@flanksource/clicky-ui/components)",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** The clicky-ui replacement for an intrinsic element name, else undefined. */
|
|
16
|
+
export function clickyReplacementFor(elementName) {
|
|
17
|
+
if (typeof elementName !== "string") return undefined;
|
|
18
|
+
return REPLACEMENTS[elementName];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const rule = {
|
|
22
|
+
meta: {
|
|
23
|
+
type: "suggestion",
|
|
24
|
+
docs: {
|
|
25
|
+
description:
|
|
26
|
+
"Reuse clicky-ui components instead of rebuilding native DOM elements by hand",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
create(context) {
|
|
30
|
+
return {
|
|
31
|
+
JSXOpeningElement(node) {
|
|
32
|
+
const element = intrinsicElementName(node);
|
|
33
|
+
const replacement = clickyReplacementFor(element);
|
|
34
|
+
if (!replacement) return;
|
|
35
|
+
context.report({
|
|
36
|
+
message:
|
|
37
|
+
`Avoid rebuilding <${element}>. Reuse ${replacement} so styling, ` +
|
|
38
|
+
"theming, density and accessibility stay consistent with clicky-ui.",
|
|
39
|
+
node,
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export default rule;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// clicky-ui/prefer-clicky-icons — icons must come from an approved source, and
|
|
2
|
+
// must be real icon components rather than emoji/text "labels".
|
|
3
|
+
//
|
|
4
|
+
// Approved sources:
|
|
5
|
+
// - @flanksource/clicky-ui/icons (generated Ui* components, e.g. UiFullscreen)
|
|
6
|
+
// - @flanksource/icons/mi (Material/brand glyphs)
|
|
7
|
+
// - @iconify/react (the <Icon> primitive / iconify icons)
|
|
8
|
+
//
|
|
9
|
+
// Anti-patterns this flags:
|
|
10
|
+
// 1. Importing icons from any other icon library (lucide, react-icons,
|
|
11
|
+
// heroicons, @mui/icons-material, phosphor, tabler, font-awesome, …).
|
|
12
|
+
// 2. Using an emoji as an icon — a bare emoji JSX child, or an emoji passed to
|
|
13
|
+
// an `icon` prop — instead of a real icon component.
|
|
14
|
+
// 3. Passing a raw iconify name string (e.g. "codicon:clock", "lucide:activity")
|
|
15
|
+
// as a value/argument instead of importing the generated Ui* component.
|
|
16
|
+
|
|
17
|
+
import { attributeName, isIconifyName, literalString } from "./clicky-ui-shared.js";
|
|
18
|
+
|
|
19
|
+
// Sources that are always fine (never flagged), even if a denylist pattern overlaps.
|
|
20
|
+
const APPROVED_SOURCES = [
|
|
21
|
+
/^@iconify\/react(\/|$)/,
|
|
22
|
+
/^@iconify-icons\//,
|
|
23
|
+
/^@flanksource\/clicky-ui\/icons(\/|$)/,
|
|
24
|
+
/^@flanksource\/icons(\/|$)/,
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
// Third-party icon libraries consumers should not pull icons from directly.
|
|
28
|
+
const ICON_LIBRARIES = [
|
|
29
|
+
/^lucide-react$/,
|
|
30
|
+
/^lucide$/,
|
|
31
|
+
/^react-icons(\/|$)/,
|
|
32
|
+
/^@heroicons\/react(\/|$)/,
|
|
33
|
+
/^@mui\/icons-material(\/|$)/,
|
|
34
|
+
/^@phosphor-icons\/react(\/|$)/,
|
|
35
|
+
/^@radix-ui\/react-icons$/,
|
|
36
|
+
/^@tabler\/icons-react$/,
|
|
37
|
+
/^react-feather$/,
|
|
38
|
+
/^feather-icons$/,
|
|
39
|
+
/^@fortawesome\//,
|
|
40
|
+
/^@ant-design\/icons(\/|$)/,
|
|
41
|
+
/^react-bootstrap-icons$/,
|
|
42
|
+
/^@primer\/octicons-react$/,
|
|
43
|
+
/^grommet-icons$/,
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
// A single emoji glyph: a pictographic code point plus its modifiers - ZWJ
|
|
47
|
+
// (\u200d), variation selector (\uFE0F), combining enclosing keycap (\u20E3).
|
|
48
|
+
// Expressed as alternation (not a character class) to avoid combining marks
|
|
49
|
+
// inside `[...]` (oxlint no-misleading-character-class).
|
|
50
|
+
const HAS_EMOJI = /\p{Extended_Pictographic}/u;
|
|
51
|
+
const ALLOWED_GLYPH = /\s|\u200d|\uFE0F|\u20E3|\p{Extended_Pictographic}/gu;
|
|
52
|
+
|
|
53
|
+
/** True when an import source is a disallowed third-party icon library. */
|
|
54
|
+
export function isUnapprovedIconImport(source) {
|
|
55
|
+
if (typeof source !== "string") return false;
|
|
56
|
+
if (APPROVED_SOURCES.some((re) => re.test(source))) return false;
|
|
57
|
+
return ICON_LIBRARIES.some((re) => re.test(source));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** True when a string is only emoji (used as a stand-in for a real icon). */
|
|
61
|
+
export function isEmojiOnly(text) {
|
|
62
|
+
if (typeof text !== "string") return false;
|
|
63
|
+
const trimmed = text.trim();
|
|
64
|
+
if (!trimmed) return false;
|
|
65
|
+
if (!HAS_EMOJI.test(trimmed)) return false;
|
|
66
|
+
// Emoji-only ⇔ nothing remains after removing every emoji glyph + modifier.
|
|
67
|
+
return trimmed.replace(ALLOWED_GLYPH, "") === "";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const APPROVED_HINT =
|
|
71
|
+
"Import icons from @flanksource/clicky-ui/icons (Ui* components), " +
|
|
72
|
+
"@flanksource/icons/mi, or @iconify/react";
|
|
73
|
+
|
|
74
|
+
const rule = {
|
|
75
|
+
meta: {
|
|
76
|
+
type: "suggestion",
|
|
77
|
+
docs: {
|
|
78
|
+
description:
|
|
79
|
+
"Import icons from approved sources and use real icon components, not emoji/text labels",
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
create(context) {
|
|
83
|
+
return {
|
|
84
|
+
ImportDeclaration(node) {
|
|
85
|
+
if (isUnapprovedIconImport(node.source?.value)) {
|
|
86
|
+
context.report({
|
|
87
|
+
message: `"${node.source.value}" is not an approved icon source. ${APPROVED_HINT}.`,
|
|
88
|
+
node,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
Literal(node) {
|
|
93
|
+
if (isIconifyName(node.value)) {
|
|
94
|
+
context.report({
|
|
95
|
+
message: `Iconify icon name "${node.value}" passed as a string. ${APPROVED_HINT} and pass the component.`,
|
|
96
|
+
node,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
JSXText(node) {
|
|
101
|
+
if (isEmojiOnly(node.value)) {
|
|
102
|
+
context.report({
|
|
103
|
+
message: `Emoji used as an icon. ${APPROVED_HINT}.`,
|
|
104
|
+
node,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
JSXAttribute(node) {
|
|
109
|
+
if (attributeName(node) !== "icon") return;
|
|
110
|
+
if (isEmojiOnly(literalString(node.value))) {
|
|
111
|
+
context.report({
|
|
112
|
+
message: `Emoji passed to \`icon\`. ${APPROVED_HINT} and pass the component.`,
|
|
113
|
+
node,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export default rule;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// clicky-ui/prefer-tailwind-classes — flag inline `style={{…}}` that hardcodes
|
|
2
|
+
// spacing/layout/typography with static values which have direct Tailwind
|
|
3
|
+
// utilities. Computed values (var()/calc()/clamp(), identifiers, expressions)
|
|
4
|
+
// are intentionally allowed — those legitimately cannot be a static class.
|
|
5
|
+
//
|
|
6
|
+
// Color properties are deliberately NOT handled here; they belong to the theme
|
|
7
|
+
// concern (clicky-ui/prefer-theme-tokens).
|
|
8
|
+
|
|
9
|
+
import { attributeName, propertyKey, styleObject } from "./clicky-ui-shared.js";
|
|
10
|
+
|
|
11
|
+
// CSS properties with a clean Tailwind utility equivalent (non-color).
|
|
12
|
+
const TAILWIND_MAPPABLE = new Set([
|
|
13
|
+
"padding",
|
|
14
|
+
"paddingTop",
|
|
15
|
+
"paddingRight",
|
|
16
|
+
"paddingBottom",
|
|
17
|
+
"paddingLeft",
|
|
18
|
+
"paddingInline",
|
|
19
|
+
"paddingBlock",
|
|
20
|
+
"margin",
|
|
21
|
+
"marginTop",
|
|
22
|
+
"marginRight",
|
|
23
|
+
"marginBottom",
|
|
24
|
+
"marginLeft",
|
|
25
|
+
"marginInline",
|
|
26
|
+
"marginBlock",
|
|
27
|
+
"gap",
|
|
28
|
+
"rowGap",
|
|
29
|
+
"columnGap",
|
|
30
|
+
"display",
|
|
31
|
+
"flexDirection",
|
|
32
|
+
"justifyContent",
|
|
33
|
+
"alignItems",
|
|
34
|
+
"textAlign",
|
|
35
|
+
"fontWeight",
|
|
36
|
+
"fontSize",
|
|
37
|
+
"lineHeight",
|
|
38
|
+
"borderRadius",
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
// Dynamic CSS functions whose result can't be a static utility class.
|
|
42
|
+
const DYNAMIC_CSS = /\b(?:var|calc|clamp|min|max|env)\(/;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* True when a style property should instead be a Tailwind class: it is a
|
|
46
|
+
* mappable property AND its value is a static literal (number, or string with
|
|
47
|
+
* no dynamic CSS function). `value` is the already-extracted literal value.
|
|
48
|
+
*/
|
|
49
|
+
export function isHandrolledStyleValue(property, value) {
|
|
50
|
+
if (!TAILWIND_MAPPABLE.has(property)) return false;
|
|
51
|
+
if (typeof value === "number") return true;
|
|
52
|
+
if (typeof value !== "string") return false;
|
|
53
|
+
return !DYNAMIC_CSS.test(value);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Extract a static literal value (string/number) from a Property value node. */
|
|
57
|
+
function staticValue(node) {
|
|
58
|
+
if (node?.type === "Literal" && (typeof node.value === "string" || typeof node.value === "number")) {
|
|
59
|
+
return node.value;
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const rule = {
|
|
65
|
+
meta: {
|
|
66
|
+
type: "suggestion",
|
|
67
|
+
docs: {
|
|
68
|
+
description:
|
|
69
|
+
"Use Tailwind utility classes instead of hand-rolling static inline styles",
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
create(context) {
|
|
73
|
+
return {
|
|
74
|
+
JSXAttribute(node) {
|
|
75
|
+
if (attributeName(node) !== "style") return;
|
|
76
|
+
const object = styleObject(node);
|
|
77
|
+
if (!object) return;
|
|
78
|
+
for (const prop of object.properties) {
|
|
79
|
+
const key = propertyKey(prop);
|
|
80
|
+
if (!key) continue;
|
|
81
|
+
const value = staticValue(prop.value);
|
|
82
|
+
if (value === undefined) continue;
|
|
83
|
+
if (isHandrolledStyleValue(key, value)) {
|
|
84
|
+
context.report({
|
|
85
|
+
message:
|
|
86
|
+
`Hand-rolled inline style \`${key}\`. Use the equivalent Tailwind ` +
|
|
87
|
+
"class (and density utilities like `p-density-4`/`gap-density-2`) " +
|
|
88
|
+
"so spacing tracks the active density.",
|
|
89
|
+
node: prop,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export default rule;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// clicky-ui/prefer-theme-tokens — keep consumers on the theme + density system
|
|
2
|
+
// instead of bypassing it. Two anti-patterns:
|
|
3
|
+
// 1. Hardcoded colors (className `text-[#fff]`, `style={{ color: "#fff" }}`)
|
|
4
|
+
// that don't flip with `[data-theme]` — use semantic tokens (text-foreground,
|
|
5
|
+
// bg-background, border-border, …).
|
|
6
|
+
// 2. Re-reading the clicky theme/density localStorage keys by hand instead of
|
|
7
|
+
// the `useTheme`/`useDensity` hooks (or ThemeProvider/DensityProvider).
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
attributeName,
|
|
11
|
+
classTokens,
|
|
12
|
+
collectStrings,
|
|
13
|
+
literalString,
|
|
14
|
+
propertyKey,
|
|
15
|
+
styleObject,
|
|
16
|
+
} from "./clicky-ui-shared.js";
|
|
17
|
+
|
|
18
|
+
// Arbitrary Tailwind color class with a literal color, e.g. `text-[#fff]`,
|
|
19
|
+
// `bg-[rgb(0,0,0)]`, `hover:border-[#abc]`.
|
|
20
|
+
const ARBITRARY_COLOR_CLASS =
|
|
21
|
+
/(?:^|:)(?:text|bg|border|ring|fill|stroke|from|via|to|outline|caret|decoration|accent|shadow|divide)-\[(?:#[0-9a-fA-F]{3,8}|(?:rgb|rgba|hsl|hsla)\()/;
|
|
22
|
+
|
|
23
|
+
// A literal CSS color value: hex, rgb()/rgba(), hsl()/hsla().
|
|
24
|
+
const HARDCODED_COLOR = /(?:#[0-9a-fA-F]{3,8}\b|\b(?:rgb|rgba|hsl|hsla)\()/;
|
|
25
|
+
|
|
26
|
+
// CSS properties whose hardcoded color should be a theme token instead.
|
|
27
|
+
const COLOR_PROPS = new Set([
|
|
28
|
+
"color",
|
|
29
|
+
"backgroundColor",
|
|
30
|
+
"background",
|
|
31
|
+
"borderColor",
|
|
32
|
+
"outlineColor",
|
|
33
|
+
"caretColor",
|
|
34
|
+
"textDecorationColor",
|
|
35
|
+
"fill",
|
|
36
|
+
"stroke",
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
const STORAGE_KEYS = new Set(["clicky-ui-theme", "clicky-ui-density"]);
|
|
40
|
+
const STORAGE_OBJECTS = new Set(["localStorage", "sessionStorage"]);
|
|
41
|
+
|
|
42
|
+
/** True for a Tailwind class hardcoding a literal color (`text-[#fff]`). */
|
|
43
|
+
export function isArbitraryColorClass(token) {
|
|
44
|
+
return typeof token === "string" && ARBITRARY_COLOR_CLASS.test(token);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** True for a literal CSS color value (hex / rgb / hsl). */
|
|
48
|
+
export function isHardcodedColorValue(value) {
|
|
49
|
+
return typeof value === "string" && HARDCODED_COLOR.test(value);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** True for a clicky-ui theme/density localStorage key. */
|
|
53
|
+
export function isClickyStorageKey(value) {
|
|
54
|
+
return typeof value === "string" && STORAGE_KEYS.has(value);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const TOKEN_HINT =
|
|
58
|
+
"use a semantic theme token (text-foreground, bg-background, border-border, …) " +
|
|
59
|
+
"so it tracks light/dark via [data-theme]";
|
|
60
|
+
|
|
61
|
+
function checkClassName(node, context) {
|
|
62
|
+
if (attributeName(node) !== "className" && attributeName(node) !== "class") return;
|
|
63
|
+
for (const token of classTokens(collectStrings(node.value))) {
|
|
64
|
+
if (isArbitraryColorClass(token)) {
|
|
65
|
+
context.report({ message: `Hardcoded color \`${token}\` — ${TOKEN_HINT}.`, node });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function checkStyleColor(node, context) {
|
|
72
|
+
if (attributeName(node) !== "style") return;
|
|
73
|
+
const object = styleObject(node);
|
|
74
|
+
if (!object) return;
|
|
75
|
+
for (const prop of object.properties) {
|
|
76
|
+
const key = propertyKey(prop);
|
|
77
|
+
if (!key || !COLOR_PROPS.has(key)) continue;
|
|
78
|
+
if (isHardcodedColorValue(literalString(prop.value))) {
|
|
79
|
+
context.report({
|
|
80
|
+
message: `Hardcoded \`${key}\` color — ${TOKEN_HINT}.`,
|
|
81
|
+
node: prop,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** `localStorage`, `window.localStorage`, `globalThis.sessionStorage`, … */
|
|
88
|
+
function isStorageObject(node) {
|
|
89
|
+
if (node?.type === "Identifier") return STORAGE_OBJECTS.has(node.name);
|
|
90
|
+
if (node?.type === "MemberExpression" && node.property?.type === "Identifier") {
|
|
91
|
+
return STORAGE_OBJECTS.has(node.property.name);
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function checkStorageCall(node, context) {
|
|
97
|
+
const callee = node.callee;
|
|
98
|
+
if (callee?.type !== "MemberExpression") return;
|
|
99
|
+
if (!isStorageObject(callee.object)) return;
|
|
100
|
+
for (const arg of node.arguments) {
|
|
101
|
+
if (isClickyStorageKey(literalString(arg))) {
|
|
102
|
+
context.report({
|
|
103
|
+
message:
|
|
104
|
+
`Reading "${arg.value}" from storage by hand. Use clicky-ui's useTheme()/` +
|
|
105
|
+
"useDensity() hooks (or ThemeProvider/DensityProvider) instead.",
|
|
106
|
+
node,
|
|
107
|
+
});
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const rule = {
|
|
114
|
+
meta: {
|
|
115
|
+
type: "problem",
|
|
116
|
+
docs: {
|
|
117
|
+
description:
|
|
118
|
+
"Use clicky-ui theme tokens and theme/density hooks instead of hardcoded colors or raw storage access",
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
create(context) {
|
|
122
|
+
return {
|
|
123
|
+
JSXAttribute(node) {
|
|
124
|
+
checkClassName(node, context);
|
|
125
|
+
checkStyleColor(node, context);
|
|
126
|
+
},
|
|
127
|
+
CallExpression(node) {
|
|
128
|
+
checkStorageCall(node, context);
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export default rule;
|