mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
fix web design system selection persistence (#621)
This commit is contained in:
parent
1bd1f3a661
commit
4c82e48e4f
2 changed files with 115 additions and 4 deletions
|
|
@ -75,6 +75,28 @@ const TAB_LABEL_KEYS: Record<CreateTab, keyof Dict> = {
|
|||
other: 'newproj.tabOther',
|
||||
};
|
||||
|
||||
export function defaultDesignSystemSelection(
|
||||
defaultDesignSystemId: string | null,
|
||||
designSystems: DesignSystemSummary[],
|
||||
): string[] {
|
||||
if (!defaultDesignSystemId) return [];
|
||||
return designSystems.some((d) => d.id === defaultDesignSystemId)
|
||||
? [defaultDesignSystemId]
|
||||
: [];
|
||||
}
|
||||
|
||||
export function buildDesignSystemCreateSelection(
|
||||
showDesignSystemPicker: boolean,
|
||||
selectedIds: string[],
|
||||
): { primary: string | null; inspirations: string[] } {
|
||||
return showDesignSystemPicker
|
||||
? {
|
||||
primary: selectedIds[0] ?? null,
|
||||
inspirations: selectedIds.slice(1),
|
||||
}
|
||||
: { primary: null, inspirations: [] };
|
||||
}
|
||||
|
||||
export function NewProjectPanel({
|
||||
skills,
|
||||
designSystems,
|
||||
|
|
@ -99,7 +121,14 @@ export function NewProjectPanel({
|
|||
// Design-system selection is now an *array* internally so the same
|
||||
// component can drive both single-select and multi-select modes without
|
||||
// duplicating state. Single-select coerces to length 0/1.
|
||||
const [selectedDsIds, setSelectedDsIds] = useState<string[]>([]);
|
||||
const initialDefaultDsSelection = useMemo(
|
||||
() => defaultDesignSystemSelection(defaultDesignSystemId, designSystems),
|
||||
[defaultDesignSystemId, designSystems],
|
||||
);
|
||||
const [selectedDsIds, setSelectedDsIds] = useState<string[]>(
|
||||
() => initialDefaultDsSelection,
|
||||
);
|
||||
const [dsSelectionTouched, setDsSelectionTouched] = useState(false);
|
||||
const [dsMulti, setDsMulti] = useState(false);
|
||||
|
||||
// Per-tab metadata. Tracked independently so switching tabs preserves
|
||||
|
|
@ -166,6 +195,11 @@ export function NewProjectPanel({
|
|||
const showDesignSystemPicker =
|
||||
tabSupportsDesignSystem && !tabDefaultSkillForcesNoDs;
|
||||
|
||||
useEffect(() => {
|
||||
if (dsSelectionTouched) return;
|
||||
setSelectedDsIds(initialDefaultDsSelection);
|
||||
}, [dsSelectionTouched, initialDefaultDsSelection]);
|
||||
|
||||
// When entering the template tab, snap to the first user-saved template
|
||||
// if there is one (and we don't already have a valid pick). The template
|
||||
// tab no longer offers a built-in fallback — the entire point is to
|
||||
|
|
@ -242,6 +276,11 @@ export function NewProjectPanel({
|
|||
});
|
||||
}
|
||||
|
||||
function handleDesignSystemChange(ids: string[]) {
|
||||
setDsSelectionTouched(true);
|
||||
setSelectedDsIds(ids);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const el = tabsRef.current;
|
||||
if (!el) return;
|
||||
|
|
@ -269,8 +308,8 @@ export function NewProjectPanel({
|
|||
// and inspiration ids to empty there so the New Project panel can't
|
||||
// accidentally bind a stale DS that the user can no longer see in the
|
||||
// form (the picker is hidden for image/video/audio).
|
||||
const primaryDs = showDesignSystemPicker ? selectedDsIds[0] ?? null : null;
|
||||
const inspirations = showDesignSystemPicker ? selectedDsIds.slice(1) : [];
|
||||
const { primary: primaryDs, inspirations } =
|
||||
buildDesignSystemCreateSelection(showDesignSystemPicker, selectedDsIds);
|
||||
const promptTemplatePick =
|
||||
tab === 'image'
|
||||
? imagePromptTemplate
|
||||
|
|
@ -379,7 +418,7 @@ export function NewProjectPanel({
|
|||
selectedIds={selectedDsIds}
|
||||
multi={dsMulti}
|
||||
onChangeMulti={setDsMulti}
|
||||
onChange={setSelectedDsIds}
|
||||
onChange={handleDesignSystemChange}
|
||||
loading={loading}
|
||||
/>
|
||||
) : null}
|
||||
|
|
|
|||
72
apps/web/tests/components/NewProjectPanel.test.tsx
Normal file
72
apps/web/tests/components/NewProjectPanel.test.tsx
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import { renderToStaticMarkup } from 'react-dom/server';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import {
|
||||
buildDesignSystemCreateSelection,
|
||||
defaultDesignSystemSelection,
|
||||
NewProjectPanel,
|
||||
} from '../../src/components/NewProjectPanel';
|
||||
import type { DesignSystemSummary, SkillSummary } from '../../src/types';
|
||||
|
||||
const skills: SkillSummary[] = [
|
||||
{
|
||||
id: 'prototype-skill',
|
||||
name: 'Prototype',
|
||||
description: 'Build prototypes',
|
||||
mode: 'prototype',
|
||||
surface: 'web',
|
||||
previewType: 'html',
|
||||
designSystemRequired: false,
|
||||
defaultFor: ['prototype'],
|
||||
triggers: [],
|
||||
upstream: null,
|
||||
hasBody: true,
|
||||
examplePrompt: 'Build a prototype.',
|
||||
},
|
||||
];
|
||||
|
||||
const designSystems: DesignSystemSummary[] = [
|
||||
{
|
||||
id: 'clay',
|
||||
title: 'Clay',
|
||||
summary: 'Friendly tactile product UI.',
|
||||
category: 'Product',
|
||||
swatches: ['#f4efe7', '#25211d'],
|
||||
},
|
||||
];
|
||||
|
||||
describe('NewProjectPanel design system defaults', () => {
|
||||
it('uses the configured default design system when it exists in the catalog', () => {
|
||||
expect(defaultDesignSystemSelection('clay', designSystems)).toEqual(['clay']);
|
||||
expect(defaultDesignSystemSelection('missing', designSystems)).toEqual([]);
|
||||
expect(defaultDesignSystemSelection(null, designSystems)).toEqual([]);
|
||||
});
|
||||
|
||||
it('shows the configured default design system as the active project selection', () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<NewProjectPanel
|
||||
skills={skills}
|
||||
designSystems={designSystems}
|
||||
defaultDesignSystemId="clay"
|
||||
templates={[]}
|
||||
promptTemplates={[]}
|
||||
onCreate={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(markup).toContain('Clay');
|
||||
expect(markup).toContain('Default');
|
||||
expect(markup).not.toContain('Freeform');
|
||||
});
|
||||
|
||||
it('keeps media project creation from inheriting a hidden design system pick', () => {
|
||||
expect(buildDesignSystemCreateSelection(true, ['clay', 'bmw'])).toEqual({
|
||||
primary: 'clay',
|
||||
inspirations: ['bmw'],
|
||||
});
|
||||
expect(buildDesignSystemCreateSelection(false, ['clay', 'bmw'])).toEqual({
|
||||
primary: null,
|
||||
inspirations: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue