Fix preset-grid orphan row for 5/6-item preset sets
PresetButtonGrid rendered every preset set into a fixed 4-column .preset-grid, so 5-item sets (RADIUS_PRESETS, SPACING_PRESETS, IMAGE_RADIUS_PRESETS, FONT_WEIGHTS, NavStylePanel's GAP_PRESETS) wrapped a single lone button onto its own row, and the 6-item TEXT_SIZES split unevenly (4+2). PresetButtonGrid now derives a column count from presets.length via defaultPresetGridColumns() -- 5-item sets get a single row of 5, 6-item sets split into two even rows of 3, and anything else keeps the classic 4-column grid -- with an optional `columns` prop for explicit overrides. This fixes every existing call site automatically rather than threading an explicit count through each one. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -84,9 +84,30 @@ interface PresetButtonGridProps {
|
|||||||
presets: { label: string; value: string }[];
|
presets: { label: string; value: string }[];
|
||||||
activeValue: string | undefined;
|
activeValue: string | undefined;
|
||||||
onSelect: (value: string) => void;
|
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<PresetButtonGridProps> = ({ presets, activeValue, onSelect }) => (
|
|
||||||
<div className="preset-grid">
|
/** 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<PresetButtonGridProps> = ({ presets, activeValue, onSelect, columns }) => {
|
||||||
|
const cols = columns ?? defaultPresetGridColumns(presets.length);
|
||||||
|
return (
|
||||||
|
<div className="preset-grid" style={{ gridTemplateColumns: `repeat(${cols}, 1fr)` }}>
|
||||||
{presets.map((p) => (
|
{presets.map((p) => (
|
||||||
<button
|
<button
|
||||||
key={p.value}
|
key={p.value}
|
||||||
@@ -97,7 +118,8 @@ export const PresetButtonGrid: React.FC<PresetButtonGridProps> = ({ presets, act
|
|||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
interface GradientSwatchGridProps {
|
interface GradientSwatchGridProps {
|
||||||
activeValue: string | undefined;
|
activeValue: string | undefined;
|
||||||
|
|||||||
Reference in New Issue
Block a user