diff --git a/craft/src/panels/right/styles/PresetButtonGrid.columns.test.ts b/craft/src/panels/right/styles/PresetButtonGrid.columns.test.ts new file mode 100644 index 0000000..415a75e --- /dev/null +++ b/craft/src/panels/right/styles/PresetButtonGrid.columns.test.ts @@ -0,0 +1,38 @@ +import { describe, it, expect } from 'vitest'; +import { defaultPresetGridColumns } from './shared'; +import { RADIUS_PRESETS, SPACING_PRESETS, IMAGE_RADIUS_PRESETS, FONT_WEIGHTS, TEXT_SIZES, FONT_FAMILIES } from '../../../constants/presets'; + +/* + * Regression: 5-item preset sets (RADIUS_PRESETS, SPACING_PRESETS, + * IMAGE_RADIUS_PRESETS, FONT_WEIGHTS, and NavStylePanel's ad-hoc + * GAP_PRESETS) left a lone orphan button on its own row under the old + * fixed 4-column .preset-grid. PresetButtonGrid now derives a column + * count from the preset length instead. + */ +describe('defaultPresetGridColumns', () => { + it('gives 5-item sets their own single row (no orphan)', () => { + expect(defaultPresetGridColumns(RADIUS_PRESETS.length)).toBe(5); + expect(defaultPresetGridColumns(SPACING_PRESETS.length)).toBe(5); + expect(defaultPresetGridColumns(IMAGE_RADIUS_PRESETS.length)).toBe(5); + expect(defaultPresetGridColumns(FONT_WEIGHTS.length)).toBe(5); + expect(defaultPresetGridColumns(5)).toBe(5); // NavStylePanel's GAP_PRESETS + }); + + it('splits 6-item sets into two even rows of 3 (was 4+2 uneven)', () => { + expect(defaultPresetGridColumns(TEXT_SIZES.length)).toBe(3); + }); + + it('keeps the classic 4-column grid for sets that already divide evenly', () => { + expect(defaultPresetGridColumns(FONT_FAMILIES.length)).toBe(4); // 8 items + expect(defaultPresetGridColumns(4)).toBe(4); + expect(defaultPresetGridColumns(3)).toBe(4); + }); + + it('never leaves a single orphan on the final row for any count 1-12', () => { + for (let n = 1; n <= 12; n++) { + const cols = defaultPresetGridColumns(n); + const isLoneOrphan = n > cols && n % cols === 1; + expect(isLoneOrphan).toBe(false); + } + }); +}); diff --git a/craft/src/panels/right/styles/shared.tsx b/craft/src/panels/right/styles/shared.tsx index 96f860a..953f948 100644 --- a/craft/src/panels/right/styles/shared.tsx +++ b/craft/src/panels/right/styles/shared.tsx @@ -84,20 +84,42 @@ interface PresetButtonGridProps { presets: { label: string; value: string }[]; activeValue: string | undefined; onSelect: (value: string) => void; + /** Explicit column count. When omitted, a column count is derived from + * `presets.length` (see `defaultPresetGridColumns`) so odd-sized preset + * sets (5, 6, ...) don't leave a lone orphan button dangling on its own + * row under the fixed 4-column grid. */ + columns?: number; } -export const PresetButtonGrid: React.FC = ({ presets, activeValue, onSelect }) => ( -
- {presets.map((p) => ( - - ))} -
-); + +/** Picks a column count that avoids a single orphan on the last row. + * 4-or-fewer presets keep the classic single row of 4. 5 gets its own + * row (5 cols). 6 splits into two even rows of 3. Anything else falls + * back to a 4- or 3-column grid depending on which divides evenly. */ +export function defaultPresetGridColumns(count: number): number { + if (count <= 4) return 4; + if (count === 5) return 5; + if (count === 6) return 3; + if (count % 4 === 0) return 4; + if (count % 3 === 0) return 3; + return 4; +} + +export const PresetButtonGrid: React.FC = ({ presets, activeValue, onSelect, columns }) => { + const cols = columns ?? defaultPresetGridColumns(presets.length); + return ( +
+ {presets.map((p) => ( + + ))} +
+ ); +}; interface GradientSwatchGridProps { activeValue: string | undefined;