pasika 0.1.4 → 0.1.6
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/eslint/pasika/index.js +3 -0
- package/dist/eslint/pasika/rules/filename-case.d.ts +2 -0
- package/dist/eslint/pasika/rules/filename-case.js +90 -0
- package/docs/agent-conventions.md +6 -0
- package/docs/code-organization-guide/code-organization-guide.md +24 -20
- package/docs/code-organization-guide/references/application-architecture-reference.md +61 -19
- package/docs/code-organization-guide/rules/component-placement-rule.md +9 -25
- package/docs/code-organization-guide/rules/configuration-rule.md +9 -9
- package/docs/code-organization-guide/rules/constants-rule.md +5 -8
- package/docs/code-organization-guide/rules/exports-and-imports-rule.md +10 -7
- package/docs/code-organization-guide/rules/folder-nesting-rule.md +1 -2
- package/docs/code-organization-guide/rules/hook-extraction-rule.md +4 -5
- package/docs/code-organization-guide/rules/interactive-component-rule.md +7 -10
- package/docs/code-organization-guide/rules/jsx-hygiene-rule.md +2 -3
- package/docs/code-organization-guide/rules/locales-rule.md +2 -4
- package/docs/code-organization-guide/rules/no-mixed-concerns-rule.md +0 -1
- package/docs/code-organization-guide/rules/repeated-structure-rule.md +1 -1
- package/docs/code-organization-guide/rules/smart-vs-dumb-component-rule.md +2 -6
- package/docs/code-organization-guide/rules/sole-state-owner-rule.md +1 -2
- package/docs/code-organization-guide/rules/types-and-schemas-rule.md +7 -6
- package/docs/code-organization-guide/rules/utilities-rule.md +1 -2
- package/docs/documentation-guide/_templates/grouped-reference.md +11 -0
- package/docs/documentation-guide/_templates/guide.md +1 -1
- package/docs/documentation-guide/_templates/single-lookup-reference.md +5 -0
- package/docs/documentation-guide/references/documentation-types-reference.md +6 -6
- package/docs/documentation-guide/rules/guide-creation-rule.md +1 -0
- package/docs/documentation-guide/rules/reference-creation-rule.md +1 -1
- package/docs/documentation-guide/rules/template-usage-rule.md +2 -1
- package/docs/styling-guide/rules/arbitrary-value-rule.md +31 -0
- package/docs/styling-guide/rules/class-composition-rule.md +30 -10
- package/docs/styling-guide/rules/component-ui-state-rule.md +53 -0
- package/docs/styling-guide/rules/component-variant-rule.md +35 -38
- package/docs/styling-guide/rules/global-stylesheet-rule.md +67 -0
- package/docs/styling-guide/rules/theme-and-utility-definition-rule.md +39 -82
- package/docs/styling-guide/styling-guide.md +8 -11
- package/package.json +1 -1
- package/docs/code-organization-guide/rules/native-prop-forwarding-rule.md +0 -38
- package/docs/documentation-guide/_templates/reference.md +0 -17
- package/docs/styling-guide/rules/color-role-naming-rule.md +0 -115
- package/docs/styling-guide/rules/component-state-rule.md +0 -26
- package/docs/styling-guide/rules/global-style-system-rule.md +0 -63
- package/docs/styling-guide/rules/style-placement-rule.md +0 -49
- package/docs/styling-guide/rules/tailwind-utility-rule.md +0 -48
- package/docs/styling-guide/rules/theme-token-rule.md +0 -32
|
@@ -3,10 +3,8 @@
|
|
|
3
3
|
When locale strings are scattered across components and constants, they are hard to find and keep consistent. This rule keeps them in one central file, puts each feature's strings together, and uses readable keys.
|
|
4
4
|
|
|
5
5
|
- All locales MUST live in the named `locales` object exported from `src/locales/index.ts`.
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
- Locales read only by modules in one feature folder MUST live in an object with the camelCase form of its feature folder name (for example, `user-settings` becomes `userSettings`).
|
|
9
|
-
- Locales read by modules in more than one feature folder or by `src/shared/`, `src/compositions/`, `src/app/`, or root support folders MUST live at the top level of `locales`.
|
|
6
|
+
- Locales read only by files in one feature folder MUST live in an object with the camelCase form of its feature folder name (for example, `user-settings` becomes `userSettings`).
|
|
7
|
+
- Locales read by files in more than one feature folder or by `src/shared/`, `src/compositions/`, `src/app/`, or root support folders MUST live at the top level of `locales`.
|
|
10
8
|
- A namespaced locale MUST be read through its full dotted path (`locales.stream.watchLiveStream`).
|
|
11
9
|
- A locale key MUST be camelCase English based on the text, unless a direct translation would be unclear or unwieldy. In that case, it MAY describe the message's purpose instead.
|
|
12
10
|
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
One component per file keeps components easy to find and change independently. This rule applies the same requirement to copied and generated source.
|
|
4
4
|
|
|
5
5
|
- A `.tsx` file that defines a component MUST contain exactly one component.
|
|
6
|
-
- A non-component module MUST use `.ts`, except when a framework requires a `.tsx` routing file.
|
|
7
6
|
|
|
8
7
|
## Incorrect — Two Components in One File
|
|
9
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Repeated markup can drift when one copy changes and another does not. This rule makes repeated structure a clear extraction trigger.
|
|
4
4
|
|
|
5
|
-
- A block of elements MUST be extracted as a named component when
|
|
5
|
+
- A block of elements MUST be extracted as a named component when two or more places use the same arrangement of elements for the same purpose. Different data or labels do not prevent extraction.
|
|
6
6
|
|
|
7
7
|
## Incorrect — Repeated Structure Kept Inline
|
|
8
8
|
|
|
@@ -2,12 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Without a file-name convention, a component's smart vs dumb ownership is invisible to reviewers from the tree alone. Without a `data-testid` matching the file's casing, tests hardcode DOM identities that break on rename or restructure.
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
- A component
|
|
7
|
-
- In this rule, an effect MUST be a subscription, external I/O, DOM manipulation, or resource lifecycle.
|
|
8
|
-
- Local UI state and callbacks that only update it MUST NOT make a component smart, including when passed to another component or directly to a native element.
|
|
9
|
-
- A dumb component MUST NOT do any of these.
|
|
10
|
-
- A component that does none of these MUST be dumb.
|
|
5
|
+
- A smart component MUST fetch data, or define `handle*` callbacks and pass them to children as `on*` props.
|
|
6
|
+
- A dumb component MUST NOT fetch data or define `handle*` callbacks for children.
|
|
11
7
|
- A smart component file name MUST be `PascalCase.tsx`.
|
|
12
8
|
- A dumb component file name MUST be `kebab-case.tsx`.
|
|
13
9
|
- A smart component with one outer DOM element in every rendered result MUST set `data-testid` on that element, and its value MUST match the component name in `PascalCase`.
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Some blocks of elements are the only consumers of a state hook. This rule extracts those blocks into components that own the hook.
|
|
4
4
|
|
|
5
|
-
- A
|
|
6
|
-
- A proper sub-block of a component's output MUST be extracted when it contains every consumer of one state hook and one clear component name describes it. A consumer declared before `return` belongs to a sub-block only when it is used exclusively by that sub-block; otherwise this rule does not require extraction. The component that owns the state is not itself an extraction trigger.
|
|
5
|
+
- A component MUST extract a named component when one part of its JSX contains every JSX expression, callback, and effect that reads one state hook's value or calls its updater.
|
|
7
6
|
|
|
8
7
|
## Incorrect — Parent Keeps Child-Only State
|
|
9
8
|
|
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
Types and schemas are easy to bury in component files or scatter across the project. This rule keeps them close to one component and gives independently used ones a consistent location.
|
|
4
4
|
|
|
5
|
-
- A type or schema
|
|
6
|
-
-
|
|
7
|
-
-
|
|
5
|
+
- A type or schema declared in a component MUST stay in that component file until another file imports it without the component where it is defined.
|
|
6
|
+
- Importing a type or schema alongside the component that defines it MUST NOT require extraction.
|
|
7
|
+
- A type or schema declared outside a component MUST stay in its file until another file needs it without using the code in that file.
|
|
8
8
|
- Extracted types and schemas MUST live in their matching `types/` or `schemas/` folder at the closest common folder (CCF) of their consumers.
|
|
9
|
-
- A `types/` or `schemas/` `index.ts` MAY define its exports directly.
|
|
10
|
-
- Types and schemas that are used together MAY be grouped in a file with a kebab-case name and named-re-exported from `index.ts`.
|
|
11
|
-
- When a `types/` or `schemas/` folder uses grouped files, its `index.ts` MUST only named-re-export those files.
|
|
12
9
|
- When a type or schema's CCF is `src/features/`, it MUST move to `src/types/` or `src/schemas/`.
|
|
10
|
+
- A `types/` or `schemas/` folder MUST either define its exports directly in `index.ts` or group related types and schemas in kebab-case files that `index.ts` named-re-exports.
|
|
11
|
+
- Consumers MUST import an extracted type or schema through the `index.ts` in that type or schema's `types/` or `schemas/` folder.
|
|
12
|
+
- A type or schema used only to implement one configuration module MUST live in that module's `types/` or `schemas/` folder.
|
|
13
|
+
- A type MAY stay in `src/config/<module>/` when its meaning is derived from the configuration that it parameterizes, even when consumers exist outside the config module.
|
|
13
14
|
|
|
14
15
|
## Incorrect — Feature and Composition Type Kept in a Feature
|
|
15
16
|
|
|
@@ -3,13 +3,12 @@
|
|
|
3
3
|
Pure functions should not be hidden in component files. This rule extracts them to a predictable `utils/` folder and keeps their imports direct.
|
|
4
4
|
|
|
5
5
|
- A pure function MUST be extracted to `utils/`, even when it has one consumer.
|
|
6
|
-
- A pure function returns the same result for the same arguments and does not read or write external mutable state, perform I/O, or manipulate the DOM.
|
|
7
|
-
- A utility used only to implement one configuration object MUST live in that object's `utils/` folder.
|
|
8
6
|
- An extracted utility MUST live in the `utils/` folder at the closest common folder (CCF) of its consumers.
|
|
9
7
|
- When a utility's CCF is `src/features/`, it MUST move to `src/utils/`.
|
|
10
8
|
- A utility MUST be imported directly without a barrel.
|
|
11
9
|
- A utility file that exports one function MUST have a name in that function's kebab-case form.
|
|
12
10
|
- Utilities that are used together MAY be grouped in a file with a kebab-case name.
|
|
11
|
+
- A utility used only to implement one configuration module MUST live in that module's `utils/` folder.
|
|
13
12
|
|
|
14
13
|
## Incorrect — Pure Function Left Beside Its Consumer
|
|
15
14
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# [Topic] Reference
|
|
2
|
+
|
|
3
|
+
[1-2 short sentences naming exactly what readers can look up here and when they should use this reference.]
|
|
4
|
+
|
|
5
|
+
## [Lookup Block Name]
|
|
6
|
+
|
|
7
|
+
[1-2 short sentences explaining what this lookup block contains and why it is included.]
|
|
8
|
+
|
|
9
|
+
[Place this lookup block's content here.]
|
|
10
|
+
|
|
11
|
+
[Repeat this section for every remaining lookup block.]
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Documentation Types Reference
|
|
2
2
|
|
|
3
|
-
Use this reference to look up the three standard Markdown document kinds in this repository.
|
|
3
|
+
Use this reference to look up the three standard Markdown document kinds in this repository.
|
|
4
4
|
|
|
5
|
-
| Kind | Shape | Template
|
|
6
|
-
| --------- | ----------------------------------------------------------------------------------------------- |
|
|
7
|
-
| Guide | A workflow-oriented document with one or more `## How To [...]` sections. |
|
|
8
|
-
| Rule | An opinionated project rule that states required, forbidden, recommended, or optional behavior. |
|
|
9
|
-
| Reference | Lookup material
|
|
5
|
+
| Kind | Shape | Template |
|
|
6
|
+
| --------- | ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
|
|
7
|
+
| Guide | A workflow-oriented document with one or more `## How To [...]` sections. | `/_templates/guide.md` |
|
|
8
|
+
| Rule | An opinionated project rule that states required, forbidden, recommended, or optional behavior. | `/_templates/rule.md` |
|
|
9
|
+
| Reference | Lookup material. A lookup block is one self-contained set of facts for a reader's question. Use the single template for one lookup block and the grouped template for several blocks organized by topic. | `/_templates/single-lookup-reference.md` or `/_templates/grouped-reference.md` |
|
|
@@ -4,6 +4,7 @@ Guides without a consistent creation process invent their own structure. This ru
|
|
|
4
4
|
|
|
5
5
|
- Each How To step MUST be concise and use one sentence.
|
|
6
6
|
- A Guide MAY reference Rules, References, and other Guides.
|
|
7
|
+
- A Guide overview MUST contain one or two short descriptive sentences about the guide's scope and purpose, and MUST NOT contain instructions or links to other documentation.
|
|
7
8
|
- Each How To step MUST link at most one documentation file total, whether that file is a Rule, Reference, or Guide.
|
|
8
9
|
- When a step links a Rule, Reference, or Guide, the step MUST name the concrete decision or result the reader will have after reading it.
|
|
9
10
|
- A step that links another Guide MUST link directly to the relevant How To section.
|
|
@@ -5,7 +5,7 @@ References without a consistent creation process scatter lookup material. This r
|
|
|
5
5
|
- Reference file names MUST match the document title in kebab-case and use the `-reference` suffix (e.g., `foo-reference.md`).
|
|
6
6
|
- A Reference MUST NOT link to Rules, Guides, or other References.
|
|
7
7
|
- A Reference MUST NOT use RFC 2119 vocabulary (`MUST`, `MUST NOT`, `SHOULD`, `SHOULD NOT`, `MAY`), because lookup material describes what exists rather than imposing requirements.
|
|
8
|
-
- A Reference
|
|
8
|
+
- A Reference overview and the overview of each headed lookup block MUST contain one or two short sentences.
|
|
9
9
|
- A Reference with a single lookup block MUST NOT add a section heading for it.
|
|
10
10
|
- A Reference with two or more lookup blocks MUST add a section heading for every block, including the first.
|
|
11
11
|
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Authoring without a template makes every doc invent its own title, orientation, and section shape. This rule keeps new docs predictable. Templates for each document kind live in `../_templates/`.
|
|
4
4
|
|
|
5
|
-
- Authors MUST start from the template
|
|
5
|
+
- Authors MUST start from the template that matches the chosen document kind and structure.
|
|
6
6
|
- Authors MUST replace each bracketed prompt with the final title, explanation, step, or lookup content it asks for.
|
|
7
7
|
- Authors MUST delete template branches that do not apply to the document being written.
|
|
8
|
+
- Documentation support assets MAY live in folders whose names start with an underscore.
|
|
8
9
|
|
|
9
10
|
## Incorrect — Template Prompts and Unused Branches Kept
|
|
10
11
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Arbitrary Value Rule
|
|
2
|
+
|
|
3
|
+
Literal arbitrary-value classes hide values used for the project's styling in component code. This rule requires every such value to have a named utility or token.
|
|
4
|
+
|
|
5
|
+
- Components MUST NOT use arbitrary-value classes for project styling. They MUST use an existing Tailwind or project utility, or define a project token or custom utility first.
|
|
6
|
+
|
|
7
|
+
## Incorrect — Literal Arbitrary Radius
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
<button className="rounded-[13px]">Save</button>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Why: the design value is hidden in markup and cannot be tracked as part of the project's explicit token set.
|
|
14
|
+
|
|
15
|
+
## Correct — Named Radius Token
|
|
16
|
+
|
|
17
|
+
```css
|
|
18
|
+
:root {
|
|
19
|
+
--radius-md: 0.375rem;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@theme inline {
|
|
23
|
+
--radius-md: var(--radius-md);
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
<button className="rounded-md">Save</button>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Why: the named value makes the radius searchable and exposes the matching Tailwind radius utility.
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
# Class Composition Rule
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`cn` is the project's helper for combining conditional classes and resolving conflicting Tailwind utilities. It gives conditional classes and a passed `className` one predictable merge point.
|
|
4
4
|
|
|
5
|
-
- Components MUST use `cn` or the project's equivalent class-merging helper for conditional classes and a
|
|
5
|
+
- Components MUST use `cn` or the project's equivalent class-merging helper for conditional classes and a `className` passed to the component.
|
|
6
6
|
- Components MUST NOT concatenate class strings with template literals or `+` when any part is conditional.
|
|
7
|
-
- A
|
|
8
|
-
-
|
|
9
|
-
- A
|
|
10
|
-
- A component MUST expose supported internal appearance and fixed-size choices through typed props rather than consumer classes.
|
|
11
|
-
- Components SHOULD group long static class lists by concern inside `cn`.
|
|
12
|
-
- Components MAY keep a short, fully static `className` string inline.
|
|
7
|
+
- A `className` passed to a component MUST contain only outer-layout utilities: margins, sizing, flex or grid item placement, and `z-index`.
|
|
8
|
+
- A component MUST expose its supported appearance and size variants through typed props, not through a passed `className` or separate class-name props for internal elements.
|
|
9
|
+
- A static class list with more than five class names MUST use `cn` with multiple string literals, each grouped by styling concern and containing no more than five class names.
|
|
13
10
|
|
|
14
11
|
## Incorrect — Conditional Classes Concatenated Manually
|
|
15
12
|
|
|
@@ -22,11 +19,34 @@ Why: the condition and merge order are embedded in a string expression, which be
|
|
|
22
19
|
## Correct — Conditional Classes Merged with `cn`
|
|
23
20
|
|
|
24
21
|
```tsx
|
|
25
|
-
export
|
|
26
|
-
return <article className={cn("surface
|
|
22
|
+
export function Card({ className, ...props }: CardProps): React.JSX.Element {
|
|
23
|
+
return <article className={cn("card-surface rounded-lg p-4", className)} {...props} />;
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
<Card className="w-full max-w-lg self-center" />;
|
|
30
27
|
```
|
|
31
28
|
|
|
32
29
|
Why: every class source is explicit, and a consumer can add outer layout such as `w-full max-w-lg self-center` without reconstructing the card's internal treatment.
|
|
30
|
+
|
|
31
|
+
## Incorrect — Long Static Class Literal
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
<article className="rounded-lg border border-border bg-card px-6 py-4 shadow-sm transition-shadow hover:shadow-md" />
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Why: one static literal contains more than five class names, so its styling concerns are difficult to scan.
|
|
38
|
+
|
|
39
|
+
## Correct — Static Classes Grouped in `cn`
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
<article
|
|
43
|
+
className={cn(
|
|
44
|
+
"rounded-lg border border-border",
|
|
45
|
+
"bg-card shadow-sm",
|
|
46
|
+
"px-6 py-4",
|
|
47
|
+
"transition-shadow hover:shadow-md",
|
|
48
|
+
)}
|
|
49
|
+
/>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Why: each literal contains at most five class names and groups one styling concern.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Component UI State Rule
|
|
2
|
+
|
|
3
|
+
Visual state communicates whether a component can be used and what will happen when it is used. This rule keeps that feedback owned by the component rather than reconstructed by every caller.
|
|
4
|
+
|
|
5
|
+
- A component MUST own a distinguishable appearance for every UI state it supports.
|
|
6
|
+
- A component MUST use Tailwind "state variants" when they can express a supported UI state.
|
|
7
|
+
- Components MUST use native semantic elements and attributes and MUST NOT use custom equivalents.
|
|
8
|
+
|
|
9
|
+
## Incorrect — State Styled Only with Classes
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
<Button className={isSaving ? "pointer-events-none opacity-50" : ""}>Save</Button>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Why: every caller has to reconstruct the saving or disabled treatment, and the button does not receive a semantic disabled state.
|
|
16
|
+
|
|
17
|
+
## Correct — State Exposed Through Component Props
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { cn } from "@/utils/cn";
|
|
21
|
+
|
|
22
|
+
type ButtonProps = {
|
|
23
|
+
children: React.ReactNode;
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
loading?: boolean;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function Button({ children, disabled = false, loading = false }: ButtonProps): React.JSX.Element {
|
|
29
|
+
const isUnavailable = disabled || loading;
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<button
|
|
33
|
+
aria-busy={loading}
|
|
34
|
+
className={cn(
|
|
35
|
+
"rounded px-3",
|
|
36
|
+
"hover:opacity-90 focus-visible:outline-2",
|
|
37
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
38
|
+
"aria-busy:animate-pulse",
|
|
39
|
+
)}
|
|
40
|
+
disabled={isUnavailable}
|
|
41
|
+
type="button"
|
|
42
|
+
>
|
|
43
|
+
{loading ? "Saving…" : children}
|
|
44
|
+
</button>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
<Button loading={isSaving}>
|
|
49
|
+
Save
|
|
50
|
+
</Button>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Why: `Button` uses native disabled and ARIA busy attributes with Tailwind state variants, while callers express only loading through its typed API.
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
# Component Variant Rule
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`cva` is Class Variance Authority, a helper for defining a component's visual options and deriving their TypeScript API. `cn` handles a standalone boolean choice without creating a variant matrix.
|
|
4
4
|
|
|
5
|
-
- A component that lets callers choose
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
- A boolean appearance
|
|
9
|
-
- A boolean appearance input MUST use a CVA variant when both boolean values have explicit treatments or when the boolean participates in a compound variant.
|
|
10
|
-
- A component MUST NOT expose separate class-name props for internal elements to vary their appearance.
|
|
11
|
-
- A component MAY expose structured content slots when callers need to provide content rather than alter internal styling.
|
|
5
|
+
- A component that lets callers choose visual options MUST define them with `cva`.
|
|
6
|
+
- A component's variant prop types MUST be derived from its `cva` definition with `VariantProps` rather than manually duplicated unions.
|
|
7
|
+
- A boolean prop that changes appearance MUST use conditional `cn()` when it only adds or removes one standalone class treatment.
|
|
8
|
+
- A boolean prop that changes appearance MUST use a CVA variant when both boolean values have explicit treatments or when the boolean participates in a compound variant.
|
|
12
9
|
|
|
13
10
|
## Incorrect — Variant Union Duplicated by Hand
|
|
14
11
|
|
|
@@ -39,8 +36,8 @@ const buttonVariants = cva("inline-flex items-center", {
|
|
|
39
36
|
lg: "h-11 px-5",
|
|
40
37
|
},
|
|
41
38
|
tone: {
|
|
42
|
-
primary: "surface
|
|
43
|
-
danger: "surface
|
|
39
|
+
primary: "primary-surface",
|
|
40
|
+
danger: "danger-surface",
|
|
44
41
|
},
|
|
45
42
|
},
|
|
46
43
|
defaultVariants: {
|
|
@@ -51,78 +48,78 @@ const buttonVariants = cva("inline-flex items-center", {
|
|
|
51
48
|
|
|
52
49
|
type ButtonProps = React.ComponentProps<"button"> & VariantProps<typeof buttonVariants>;
|
|
53
50
|
|
|
54
|
-
export
|
|
51
|
+
export function Button({ size, tone, className, ...props }: ButtonProps): React.JSX.Element {
|
|
55
52
|
return <button className={cn(buttonVariants({ size, tone }), className)} {...props} />;
|
|
56
53
|
}
|
|
57
54
|
```
|
|
58
55
|
|
|
59
56
|
Why: the CVA definition owns the accepted values, defaults, and classes, while `VariantProps` derives the public TypeScript API from the same source.
|
|
60
57
|
|
|
61
|
-
## Incorrect — Standalone Boolean Added to CVA
|
|
58
|
+
## Incorrect — Standalone Boolean Variant Added to CVA
|
|
62
59
|
|
|
63
60
|
```tsx
|
|
64
61
|
const buttonVariants = cva("inline-flex", {
|
|
65
62
|
variants: {
|
|
66
|
-
|
|
67
|
-
true: "
|
|
63
|
+
elevated: {
|
|
64
|
+
true: "shadow-md",
|
|
68
65
|
false: "",
|
|
69
66
|
},
|
|
70
67
|
},
|
|
71
68
|
});
|
|
72
69
|
```
|
|
73
70
|
|
|
74
|
-
Why: one boolean merely adds a standalone treatment and has no compound relationship, so CVA adds configuration without a styling matrix.
|
|
71
|
+
Why: one boolean variant merely adds a standalone treatment and has no compound relationship, so CVA adds configuration without a styling matrix.
|
|
75
72
|
|
|
76
|
-
## Correct — Standalone Boolean Uses `cn`
|
|
73
|
+
## Correct — Standalone Boolean Variant Uses `cn`
|
|
77
74
|
|
|
78
75
|
```tsx
|
|
79
|
-
<button className={cn(buttonVariants({ size, tone }),
|
|
76
|
+
<button className={cn(buttonVariants({ size, tone }), elevated && "shadow-md", className)} />
|
|
80
77
|
```
|
|
81
78
|
|
|
82
|
-
Why: the named appearance choices stay in CVA while the independent boolean remains a direct conditional class.
|
|
79
|
+
Why: the named appearance choices stay in CVA while the independent boolean variant remains a direct conditional class.
|
|
83
80
|
|
|
84
|
-
## Incorrect — Compound Boolean Styling Split Across Conditions
|
|
81
|
+
## Incorrect — Compound Boolean Variant Styling Split Across Conditions
|
|
85
82
|
|
|
86
83
|
```tsx
|
|
87
84
|
<button
|
|
88
85
|
className={cn(
|
|
89
|
-
buttonVariants({
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
86
|
+
buttonVariants({ size }),
|
|
87
|
+
square && "justify-center",
|
|
88
|
+
square && size === "sm" && "w-8 px-0",
|
|
89
|
+
square && size === "lg" && "w-11 px-0",
|
|
93
90
|
)}
|
|
94
91
|
/>
|
|
95
92
|
```
|
|
96
93
|
|
|
97
|
-
Why: the boolean changes treatment according to another variant, but the styling matrix is spread across conditional expressions outside CVA.
|
|
94
|
+
Why: the boolean variant changes treatment according to another variant, but the styling matrix is spread across conditional expressions outside CVA.
|
|
98
95
|
|
|
99
|
-
## Correct — Boolean with Compound Styling Uses CVA
|
|
96
|
+
## Correct — Boolean Variant with Compound Styling Uses CVA
|
|
100
97
|
|
|
101
98
|
```tsx
|
|
102
99
|
const buttonVariants = cva("inline-flex", {
|
|
103
100
|
variants: {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
101
|
+
size: {
|
|
102
|
+
sm: "h-8 px-3",
|
|
103
|
+
lg: "h-11 px-5",
|
|
107
104
|
},
|
|
108
|
-
|
|
109
|
-
true: "
|
|
110
|
-
false: "
|
|
105
|
+
square: {
|
|
106
|
+
true: "justify-center",
|
|
107
|
+
false: "justify-start",
|
|
111
108
|
},
|
|
112
109
|
},
|
|
113
110
|
compoundVariants: [
|
|
114
111
|
{
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
className: "
|
|
112
|
+
size: "sm",
|
|
113
|
+
square: true,
|
|
114
|
+
className: "w-8 px-0",
|
|
118
115
|
},
|
|
119
116
|
{
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
className: "
|
|
117
|
+
size: "lg",
|
|
118
|
+
square: true,
|
|
119
|
+
className: "w-11 px-0",
|
|
123
120
|
},
|
|
124
121
|
],
|
|
125
122
|
});
|
|
126
123
|
```
|
|
127
124
|
|
|
128
|
-
Why: both boolean values have explicit treatment and
|
|
125
|
+
Why: both boolean values have explicit treatment and the square appearance changes by size, so CVA owns the complete styling matrix.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Global Stylesheet Rule
|
|
2
|
+
|
|
3
|
+
Use the global stylesheet to define Tailwind, the shared theme, and base styles. This rule keeps all of the project's global CSS in one entry point and ordered predictably.
|
|
4
|
+
|
|
5
|
+
- A repository MUST have one global stylesheet entry point that registers Tailwind.
|
|
6
|
+
- The project's global CSS MUST live in the global stylesheet entry point and MUST NOT be imported from another file.
|
|
7
|
+
- The global stylesheet MUST reset Tailwind's default theme with `--*: initial`.
|
|
8
|
+
- Every value used for the project's styling MUST be defined as a CSS variable in `:root`, even when no theme selector overrides it. A Tailwind theme variable MUST reference that CSS variable through `@theme inline`.
|
|
9
|
+
- Style declarations added by the project inside global selectors MUST use `@apply`.
|
|
10
|
+
- The global stylesheet MUST order imports, `@custom-variant` definitions, `:root` variables and the selectors that override them, `@theme` definitions, custom utilities, base styles, and keyframes in that order.
|
|
11
|
+
- The global base layer MUST apply `base-canvas` and `base-ink` to the document body as the default page pair.
|
|
12
|
+
|
|
13
|
+
## Incorrect — Global Styles Split Across Unrelated Files
|
|
14
|
+
|
|
15
|
+
```css
|
|
16
|
+
/* globals.css */
|
|
17
|
+
@import "tailwindcss";
|
|
18
|
+
|
|
19
|
+
@theme {
|
|
20
|
+
--spacing: 0.25rem;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* editor.css */
|
|
24
|
+
@import "tailwindcss";
|
|
25
|
+
|
|
26
|
+
@theme {
|
|
27
|
+
--font-editor: Georgia, serif;
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Why: Tailwind and theme definitions are split across competing global entry points, so neither file clearly owns the system.
|
|
32
|
+
|
|
33
|
+
## Correct — One Global Stylesheet Owns the System
|
|
34
|
+
|
|
35
|
+
```css
|
|
36
|
+
@import "tailwindcss";
|
|
37
|
+
|
|
38
|
+
:root {
|
|
39
|
+
--spacing: 0.25rem;
|
|
40
|
+
--base-canvas: #ffffff;
|
|
41
|
+
--base-ink: #111827;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@theme {
|
|
45
|
+
--*: initial;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@theme inline {
|
|
49
|
+
--spacing: var(--spacing);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@utility bg-base-canvas {
|
|
53
|
+
@apply bg-(--base-canvas);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@utility text-base-ink {
|
|
57
|
+
@apply text-(--base-ink);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@layer base {
|
|
61
|
+
body {
|
|
62
|
+
@apply bg-base-canvas text-base-ink;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Why: one entry point registers Tailwind, defines the shared system, and applies document defaults in a predictable order.
|