Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fiery-emus-agree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@fuzdev/fuz_css': patch
---

fix: bug with interpreter disallowing class names matching modifiers
5 changes: 5 additions & 0 deletions .changeset/rare-cars-prove.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@fuzdev/fuz_css': minor
---

feat: replace `.compact` with `.sm` and add `.xs-.xl`
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ See `GenFuzCssOptions` and `VitePluginFuzCssOptions` types for configuration.
- **Token classes** - Map to style variables: `p_md`, `color_a_50`, `gap_lg`
- **Composite classes** - Multi-property shortcuts: `box`, `column`, `row`,
`ellipsis`, `pixelated`, `circular`, `selectable`, `clickable`, `pane`,
`panel`, `compact` (consider `.loose`/`.spacious` counterpart), `icon_button`,
`plain`, `menuitem`, `chevron`, `chip`
`panel`, `sm` (tighter sizing), `md` (default sizing / cascade reset),
`icon_button`, `plain`, `menuitem`, `chevron`, `chip`
- **Literal classes** - CSS `property:value` syntax: `display:flex`, `opacity:50%`

All class types support modifiers: responsive (`md:`), state (`hover:`),
Expand Down
23 changes: 17 additions & 6 deletions src/lib/css_class_composites.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,7 @@ export const css_class_composites: Record<string, CssClassDefinition | undefined
background-color: var(--fg_10);
`,
},
// TODO consider `.loose`/`.spacious` counterpart
compact: {
sm: {
comment:
'Tighter sizing by overriding variables, cascading to children. Works on individual elements or containers.',
declaration: `
Expand All @@ -118,18 +117,30 @@ export const css_class_composites: Record<string, CssClassDefinition | undefined
--input_height_sm: var(--space_xl2);
--input_padding_x: var(--space_sm);
--min_height: var(--space_xl3);
--border_radius: var(--border_radius_xs2);
--icon_size: var(--icon_size_sm);
--menuitem_padding: var(--space_xs4) var(--space_xs3);
--flow_margin: var(--space_md);
`,
},
md: {
comment: 'Explicit default sizing, useful as a cascade reset within a sized parent like .sm.',
declaration: `
--font_size: var(--font_size_md);
--input_height: var(--space_xl5);
--input_height_sm: var(--space_xl4);
--input_padding_x: var(--space_lg);
--min_height: var(--input_height);
--icon_size: var(--icon_size_md);
--menuitem_padding: var(--space_xs3) var(--space_xs);
--flow_margin: var(--space_lg);
`,
},
mb_flow: {
comment: 'Flow-aware margin-bottom that responds to --flow_margin overrides like .compact.',
comment: 'Flow-aware margin-bottom that responds to --flow_margin overrides like .sm.',
declaration: 'margin-bottom: var(--flow_margin, var(--space_lg));',
},
mt_flow: {
comment: 'Flow-aware margin-top that responds to --flow_margin overrides like .compact.',
comment: 'Flow-aware margin-top that responds to --flow_margin overrides like .sm.',
declaration: 'margin-top: var(--flow_margin, var(--space_lg));',
},
icon_button: {
Expand Down Expand Up @@ -166,8 +177,8 @@ export const css_class_composites: Record<string, CssClassDefinition | undefined
menuitem: {
ruleset: `
.menuitem {
--border_radius: 0;
--border_color: var(--border_color_30);
border-radius: 0;
position: relative;
z-index: 2;
cursor: pointer;
Expand Down
17 changes: 11 additions & 6 deletions src/lib/css_class_interpreters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,15 @@ export const modified_class_interpreter: CssClassDefinitionInterpreter = {
const class_name = matched[0];
const segments = extract_segments(class_name);

// Extract modifiers from the front
const result = extract_and_validate_modifiers(segments, class_name);
// The last segment is always the base class name — only preceding segments
// can be modifiers. This avoids ambiguity when a class name collides with
// a modifier name (e.g., `sm` and `md` are both breakpoint modifiers and
// size composite classes).
const base_class_name = segments[segments.length - 1]!;
const modifier_segments = segments.slice(0, -1);

// Extract modifiers from the preceding segments
const result = extract_and_validate_modifiers(modifier_segments, class_name);

if (!result.ok) {
// Modifier validation error - let css_literal_interpreter try
Expand All @@ -33,13 +40,11 @@ export const modified_class_interpreter: CssClassDefinitionInterpreter = {

const {modifiers, remaining} = result;

// Must have exactly one remaining segment (the base class name)
if (remaining.length !== 1) {
// All preceding segments must be valid modifiers (none left over)
if (remaining.length !== 0) {
return null;
}

const base_class_name = remaining[0]!;

// Check if the base class is known
const base_class = ctx.class_definitions[base_class_name];
if (!base_class) {
Expand Down
8 changes: 4 additions & 4 deletions src/lib/css_literal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export const load_css_properties = async (): Promise<Set<string>> => {
* Custom properties (--*) always return true.
*
* @param property - the CSS property name to validate
* @param properties - Set of valid CSS properties from `load_css_properties()`.
* @param properties - Set of valid CSS properties from `load_css_properties()`
* Pass `null` to skip validation.
* @returns true if valid CSS property or custom property
*/
Expand Down Expand Up @@ -140,7 +140,7 @@ const find_closest_match = (typo: string, candidates: Iterable<string>): string
* Suggests a correct property name for a typo using Levenshtein distance.
*
* @param typo - the mistyped property name
* @param properties - Set of valid CSS properties from `load_css_properties()`.
* @param properties - Set of valid CSS properties from `load_css_properties()`
* Pass `null` to skip suggestions.
* @returns the suggested property or null if no close match (Levenshtein distance > 2)
*/
Expand Down Expand Up @@ -444,7 +444,7 @@ export const extract_and_validate_modifiers = (
* Parses a CSS-literal class name into its components.
*
* @param class_name - the class name to parse
* @param css_properties - Set of valid CSS properties from `load_css_properties()`.
* @param css_properties - Set of valid CSS properties from `load_css_properties()`
* Pass `null` to skip property validation.
* @returns `CssLiteralParseResult` with parsed data or error
*/
Expand Down Expand Up @@ -594,7 +594,7 @@ export interface CssLiteralOutput {
*
* @param class_name - the class name to interpret
* @param escaped_class_name - the CSS-escaped version of the class name
* @param css_properties - Set of valid CSS properties from `load_css_properties()`.
* @param css_properties - Set of valid CSS properties from `load_css_properties()`
* Pass `null` to skip property validation.
* @returns result with output and warnings on success, or error on failure
*/
Expand Down
6 changes: 3 additions & 3 deletions src/lib/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ Respects `hidden="until-found"` for find-in-page support.
}

/* heading margins use calc multipliers on --flow_margin so they scale proportionally
with .compact while preserving hierarchy. multipliers are derived from the space
with .sm while preserving hierarchy. multipliers are derived from the space
scale (sqrt(GR) ≈ 1.272 per step). margin-top is 2 steps above margin-bottom. */
:where(h1:not(.unstyled)) {
--font_size: var(--font_size_xl3);
Expand Down Expand Up @@ -255,7 +255,7 @@ currently can break based on DOM structure - https://developer.mozilla.org/en-US
-webkit-hyphens: none;
hyphens: none;
color: var(--text_color);
background-color: var(--fg_10);
background-color: var(--fg_05);
padding: 0 var(--space_xs2);
border-radius: var(--border_radius, var(--border_radius_xs));
}
Expand Down Expand Up @@ -348,7 +348,7 @@ so for consistent visuals we opt to include no active state */
height: var(--input_height_sm);
min-width: var(--input_height_sm);
min-height: var(--input_height_sm);
--border_radius: var(--border_radius_xs);
border-radius: var(--border_radius, var(--border_radius_xs));
--input_padding_x: 0;
--checkbox_content: var(--checkbox_content_empty, '');
}
Expand Down
40 changes: 20 additions & 20 deletions src/routes/docs/buttons/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -226,49 +226,49 @@
</TomeSection>

<TomeSection>
<TomeSectionHeader text="With .compact">
With <code>.compact</code>
<TomeSectionHeader text="With .sm">
With <code>.sm</code>
</TomeSectionHeader>
<p>
The <code>.compact</code>
The <code>.sm</code>
<TomeLink name="classes" hash="#Composite-classes">composite class</TomeLink> sizes things more
tightly with smaller fonts, inputs, padding, border radii, and flow margins.
</p>
<Code content={`<button class="compact">compact</button>`} />
<Code content={`<button>normal</button>\n<button class="sm">sm</button>`} />
<div class="row gap_xs mb_lg">
<button type="button" class="compact">compact</button>
<button type="button">normal</button>
<button type="button" class="sm">sm</button>
</div>
<p><code>.compact</code> with <code>.plain</code> and <code>.icon_button</code>:</p>
<p><code>.sm</code> with <code>.plain</code> and <code>.icon_button</code>:</p>
<Code
content={`<button>+++</button>\n<button class="compact">+++</button>\n<button class="compact plain">+++</button>\n<button class="compact icon_button">+++</button>\n<button class="compact plain icon_button">+++</button>`}
content={`<button>+++</button>\n<button class="sm">+++</button>\n<button class="sm plain">+++</button>\n<button class="sm icon_button">+++</button>\n<button class="sm plain icon_button">+++</button>`}
/>
<div class="row gap_sm mb_lg">
<button type="button">+++</button>
<button type="button" class="compact">+++</button>
<button type="button" class="compact plain">+++</button>
<button type="button" class="compact icon_button">+++</button>
<button type="button" class="compact plain icon_button">+++</button>
<button type="button" class="sm">+++</button>
<button type="button" class="sm plain">+++</button>
<button type="button" class="sm icon_button">+++</button>
<button type="button" class="sm plain icon_button">+++</button>
</div>
<p><code>.compact</code> with colors:</p>
<p><code>.sm</code> with colors:</p>
<Code
content={`<button class="compact color_h">color_h</button>\n<button class="compact color_g">color_g</button>\n<button class="compact color_d selected">color_d</button>`}
content={`<button class="sm color_h">color_h</button>\n<button class="sm color_g">color_g</button>\n<button class="sm color_d selected">color_d</button>`}
/>
<div class="row gap_xs mb_lg">
<button type="button" class="compact color_h">color_h</button>
<button type="button" class="compact color_g">color_g</button>
<button type="button" class="compact color_d selected">color_d</button>
<button type="button" class="sm color_h">color_h</button>
<button type="button" class="sm color_g">color_g</button>
<button type="button" class="sm color_d selected">color_d</button>
</div>
<p>
<code>.compact</code> overrides custom properties, so children inherit compactness:
<code>.sm</code> overrides custom properties, so children inherit the sizing:
</p>
<Code
content={`<div class="compact row gap_sm">\n\t<button>one</button>\n\t<button class="plain">to</button>\n\t<button class="color_a">3</button>\n</div>`}
content={`<div class="sm row gap_sm">\n\t<button>one</button>\n\t<button class="plain">to</button>\n\t<button class="color_a">3</button>\n</div>`}
/>
<div class="compact row gap_sm">
<div class="sm row gap_sm">
<button type="button">one</button>
<button type="button" class="plain">to</button>
<button type="button" class="color_a">3</button>
<button type="button" class="color_j">3</button>
</div>
</TomeSection>
</TomeContent>
27 changes: 14 additions & 13 deletions src/routes/docs/chips/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -63,34 +63,35 @@
</TomeSection>

<TomeSection>
<TomeSectionHeader text="With .compact">
With <code>.compact</code>
<TomeSectionHeader text="With .sm">
With <code>.sm</code>
</TomeSectionHeader>
<p>
The <code>.compact</code>
<TomeLink name="classes" hash="#Composite-classes">composite class</TomeLink> provides tighter sizing
The <code>.sm</code>
<TomeLink name="classes" hash="#Composite-classes">composite class</TomeLink> applies tighter sizing
with smaller fonts, inputs, padding, border radii, and flow margins.
</p>
<Code content={`<span class="chip compact">compact</span>`} />
<Code content={`<span class="chip">normal</span>\n<span class="chip sm">small</span>`} />
<div class="row gap_sm mb_lg">
<span class="chip compact">compact</span>
<span class="chip">normal</span>
<span class="chip sm">small</span>
</div>
<Code
content={`<span class="chip compact color_a">color_a</span>\n<span class="chip compact color_b">color_b</span>\n<span class="chip compact color_c">color_c</span>`}
content={`<span class="chip color_a">normal</span>\n<span class="chip sm color_a">color_a</span>\n<span class="chip sm color_b">color_b</span>\n<span class="chip sm color_c">color_c</span>`}
/>
<div class="row gap_sm mb_lg">
<span class="chip compact color_a">color_a</span>
<span class="chip compact color_b">color_b</span>
<span class="chip compact color_c">color_c</span>
<span class="chip color_a">normal</span>
<span class="chip color_a sm">color_a</span>
<span class="chip color_b sm">color_b</span>
<span class="chip color_c sm">color_c</span>
</div>
<p>
<code>.compact</code> overrides custom properties, so children are compact too:
<code>.sm</code> overrides custom properties, so children inherit the sizing:
</p>
<Code
content={`<div class="compact row gap_sm">\n\t<span class="chip">one</span>\n\t<span class="chip color_d">two</span>\n\t<a class="chip color_e">three</a>\n</div>`}
content={`<div class="sm row gap_sm">\n\t<span class="chip">one</span>\n\t<span class="chip color_d">two</span>\n\t<a class="chip color_e">three</a>\n</div>`}
/>
<div class="compact row gap_sm">
<div class="sm row gap_sm">
<span class="chip">one</span>
<span class="chip color_d">two</span>
<!-- svelte-ignore a11y_missing_attribute -->
Expand Down
3 changes: 2 additions & 1 deletion src/routes/docs/classes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -1055,7 +1055,8 @@ export const gen = gen_fuz_css({
<li><code>.icon_button</code> - icon button styling</li>
<li><code>.pixelated</code> - crisp pixel-art rendering</li>
<li><code>.circular</code> - 50% border-radius</li>
<li><code>.compact</code> - tighter sizing, cascading to children</li>
<li><code>.sm</code> - tighter sizing, cascading to children</li>
<li><code>.md</code> - default sizing, cascade reset</li>
<li><code>.mb_flow</code> - flow-aware margin-bottom</li>
<li><code>.mt_flow</code> - flow-aware margin-top</li>
</ul>
Expand Down
14 changes: 7 additions & 7 deletions src/routes/docs/colors/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@
'primary',
'success',
'error/danger',
'secondary/accent',
'tertiary/highlight',
'quaternary/muted',
'quinary/decorative',
'senary/caution',
'septenary/info',
'octonary/flourish',
'accent/secondary',
'highlight/tertiary',
'muted/quaternary',
'decorative/quinary',
'caution/senary',
'info/septenary',
'flourish/octonary',
];
</script>

Expand Down
12 changes: 6 additions & 6 deletions src/routes/docs/forms/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -205,25 +205,25 @@
</TomeSection>

<TomeSection>
<TomeSectionHeader text="With .compact">
With <code>.compact</code>
<TomeSectionHeader text="With .sm">
With <code>.sm</code>
</TomeSectionHeader>
<p>
The <code>.compact</code>
The <code>.sm</code>
<TomeLink name="classes" hash="#Composite-classes">composite class</TomeLink> provides tighter sizing
with smaller fonts, inputs, padding, border radii, and flow margins. Apply directly or on a container
to cascade to children.
</p>
<Code
content={`<form class="compact">
content={`<form class="sm">
...
</form>`}
/>
<div class="display:flex gap_lg">
<div class="width_atmost_sm">
<form class="compact">
<form class="sm">
<fieldset>
<legend>.compact</legend>
<legend>.sm</legend>
<label>
<div class="title">name</div>
<input placeholder=">" />
Expand Down
6 changes: 3 additions & 3 deletions src/routes/docs/semantic/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,13 @@
/>
<p>
The <code>--flow_margin</code> variable is unset by default, falling back to
<code>var(--space_lg)</code>. Override classes like <code>.compact</code> set
<code>var(--space_lg)</code>. Size composite classes like <code>.sm</code> set
<code>--flow_margin</code> to tighten vertical rhythm for all flow elements and headings.
</p>
<p>
For elements not in the flow list, use the <code>.mb_flow</code> and <code>.mt_flow</code>
composite classes to get the same compact-responsive spacing. Use <code>.mb_lg</code> when you
want a fixed value that ignores <code>.compact</code>.
composite classes to get the same size-responsive spacing. Use <code>.mb_lg</code> when you
want a fixed value that ignores <code>.sm</code>.
</p>
<aside>
⚠️ The <code>:not(:last-child)</code> creates unfortunate edge cases by coupling structure to
Expand Down
Loading
Loading