PluginProbe
Extendify / 3.2.2
Extendify v3.2.2
3.2.2 3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 All 128 releases
← All changes | src/Agent/workflows/theme/components/SelectThemeVariation.jsx +121 -49 3.1.4 → 3.2.2 View file →
@@ -1,35 +1,72 @@
1 +import { usePaletteOverride } from '@agent/hooks/usePaletteOverride';
2 +import { usePalettes } from '@agent/hooks/usePalettes';
1 3 import { useThemeVariations } from '@agent/hooks/useThemeVariations';
2 4 import { useVariationOverride } from '@agent/hooks/useVariationOverride';
3 5 import { useChatStore } from '@agent/state/chat';
6 +import { paletteDuotone } from '@shared/lib/palette-preview';
7 +import { samplePalettes } from '@shared/lib/palettes';
4 8 import { useEffect, useMemo, useRef, useState } from '@wordpress/element';
5 9 import { __ } from '@wordpress/i18n';
6 10
11 +const VISIBLE_LIMIT = 8;
12 +
7 13 export const SelectThemeVariation = ({ onConfirm, onCancel }) => {
8 14 const [css, setCss] = useState('');
9 15 const [selected, setSelected] = useState(null);
10 16 const [duotoneTheme, setDuotoneTheme] = useState(null);
11 - const { undoChange } = useVariationOverride({ css, duotoneTheme });
17 + const {
18 + palettes,
19 + css: paletteCss,
20 + preferred,
21 + isLoading: palettesLoading,
22 + } = usePalettes();
23 + const onPalettes = Boolean(palettes?.length);
24 + const { variations, isLoading: variationsLoading } = useThemeVariations({
25 + enabled: !palettesLoading && !onPalettes,
26 + });
27 + const isLoading = palettesLoading || variationsLoading;
12 28
29 + // A palette layers over core's global styles; a variation replaces them.
30 + const { undoChange: undoPalette } = usePaletteOverride({
31 + css: onPalettes ? css : '',
32 + duotoneTheme: onPalettes ? duotoneTheme : null,
33 + });
34 + const { undoChange: undoVariation } = useVariationOverride({
35 + css: onPalettes ? '' : css,
36 + duotoneTheme: onPalettes ? null : duotoneTheme,
37 + });
38 + const undoChange = onPalettes ? undoPalette : undoVariation;
39 +
13 40 const confirmed = useRef(false);
41 + const undo = useRef(undoChange);
14 42 useEffect(() => {
43 + undo.current = undoChange;
44 + }, [undoChange]);
45 + useEffect(() => {
15 46 return () => {
16 - if (!confirmed.current) undoChange();
47 + if (!confirmed.current) undo.current();
17 48 };
18 49 }, []);
19 - const { variations, isLoading } = useThemeVariations();
20 - const noVariations = !variations || variations.length === 0;
21 50 const { addMessage, messages } = useChatStore();
22 51
23 - const shuffled = useMemo(
24 - () => (variations ? variations.sort(() => Math.random() - 0.5) : []),
25 - [variations],
52 + // Drawn per mount, so the immutable SWR cache cannot freeze one set.
53 + const shown = useMemo(
54 + () => samplePalettes(palettes, preferred, VISIBLE_LIMIT),
55 + [palettes, preferred],
26 56 );
27 57
58 + const options = useMemo(
59 + () =>
60 + onPalettes ? paletteOptions(shown, paletteCss) : themeOptions(variations),
61 + [onPalettes, shown, paletteCss, variations],
62 + );
63 + const noOptions = options.length === 0;
64 +
28 65 const handleConfirm = () => {
29 66 if (!selected) return;
30 - const variation = variations.find((v) => v.title === selected);
31 - if (!variation) {
67 + const option = options.find(({ key }) => key === selected);
68 + if (!option) {
32 69 // translators: A chat message shown to the user when their selected color variation cannot be applied
33 70 const content = __(
34 71 'We were unable to apply your selected colors. Please try again.',
35 72 'extendify-local',
@@ -38,13 +75,13 @@
38 75 onCancel();
39 76 return;
40 77 }
41 78 confirmed.current = true;
42 - onConfirm({ data: { variation }, shouldRefreshPage: true });
79 + onConfirm({ data: option.data, shouldRefreshPage: true });
43 80 };
44 81
45 82 useEffect(() => {
46 - if (isLoading || !noVariations) return;
83 + if (isLoading || !noOptions) return;
47 84 const timer = setTimeout(() => onCancel(), 100);
48 85 // translators: A chat message shown to the user
49 86 const content = __(
50 87 'We were unable to find any colors for your theme',
@@ -53,9 +90,9 @@
53 90 const last = messages.at(-1)?.details?.content;
54 91 if (content === last) return () => clearTimeout(timer);
55 92 addMessage('message', { role: 'assistant', content, error: true });
56 93 return () => clearTimeout(timer);
57 - }, [addMessage, onCancel, noVariations, messages, isLoading]);
94 + }, [addMessage, onCancel, noOptions, messages, isLoading]);
58 95
59 96 if (isLoading) {
60 97 return (
61 98 <div className="min-h-24 p-2 text-center text-sm">
@@ -63,39 +100,42 @@
63 100 </div>
64 101 );
65 102 }
66 103
67 - if (noVariations) return null;
104 + if (noOptions) return null;
68 105
69 106 return (
70 - <div className="mb-4 ms-12 me-2 flex flex-col rounded-lg border border-gray-300 bg-gray-50">
107 + <div className="mb-4 ms-2 me-2 flex flex-col rounded-lg border border-gray-300 bg-gray-50">
71 108 <div className="rounded-lg border-b border-gray-300 bg-white">
72 109 <div className="grid grid-cols-2 gap-2 p-3">
73 - {shuffled?.slice(0, 10)?.map(({ title, css, settings }) => (
74 - <button
75 - key={title}
76 - style={{ backgroundColor: getColor(settings, 'background') }}
77 - type="button"
78 - className={`relative flex w-full items-center justify-center overflow-hidden rounded-lg border border-gray-300 bg-none p-2 text-center text-sm ${
79 - selected === title ? 'ring ring-design-main ring-wp' : ''
80 - }`}
81 - onClick={() => {
82 - setSelected(title);
83 - setCss(css);
84 - setDuotoneTheme(settings?.color?.duotone?.theme);
85 - }}
86 - >
87 - <div className="flex max-w-fit items-center justify-center -space-x-4 rounded-lg rtl:space-x-reverse">
88 - {getColors(settings)?.map((color, i) => (
89 - <div
90 - key={title + color + i}
91 - style={{ backgroundColor: color }}
92 - className="size-6 shrink-0 overflow-visible rounded-full border border-white md:size-7"
93 - ></div>
94 - ))}
95 - </div>
96 - </button>
97 - ))}
110 + {options.map(
111 + ({ key, title, background, swatches, previewCss, duotone }) => (
112 + <button
113 + key={key}
114 + style={{ backgroundColor: background }}
115 + type="button"
116 + aria-label={title}
117 + className={`relative flex w-full items-center justify-center overflow-hidden rounded-lg border border-gray-300 bg-none p-2 text-center text-sm ${
118 + selected === key ? 'ring ring-design-main ring-wp' : ''
119 + }`}
120 + onClick={() => {
121 + setSelected(key);
122 + setCss(previewCss);
123 + setDuotoneTheme(duotone);
124 + }}
125 + >
126 + <div className="flex max-w-fit items-center justify-center -space-x-4 rounded-lg rtl:space-x-reverse">
127 + {swatches.map((color, i) => (
128 + <div
129 + key={key + color + i}
130 + style={{ backgroundColor: color }}
131 + className="size-6 shrink-0 overflow-visible rounded-full border border-white md:size-7"
132 + ></div>
133 + ))}
134 + </div>
135 + </button>
136 + ),
137 + )}
98 138 </div>
99 139 </div>
100 140 <div className="flex justify-start gap-2 p-3">
101 141 <button
@@ -117,17 +157,49 @@
117 157 </div>
118 158 );
119 159 };
120 160
121 -const getColor = (settings, colorName) => {
122 - return settings?.color?.palette?.theme.find((item) => item.slug === colorName)
161 +// `colors` also carries tint1-3 card backgrounds, never swatches.
162 +const SWATCH_ROLES = [
163 + 'foreground',
164 + 'primary',
165 + 'secondary',
166 + 'tertiary',
167 + 'foregroundAlt',
168 +];
169 +
170 +// Only `colors` is guaranteed hex; settings.color.palette carries var() refs.
171 +const paletteOptions = (palettes, css) =>
172 + palettes.map((palette) => ({
173 + key: palette.slug,
174 + title: palette.title || palette.slug,
175 + background: palette.colors?.background,
176 + swatches: SWATCH_ROLES.map((role) => palette.colors?.[role]).filter(
177 + Boolean,
178 + ),
179 + previewCss: css?.[palette.slug] ?? '',
180 + duotone: paletteDuotone(palette),
181 + data: { colorPalette: palette.slug },
182 + }));
183 +
184 +const themeOptions = (variations) =>
185 + [...(variations ?? [])]
186 + .sort(() => Math.random() - 0.5)
187 + .slice(0, VISIBLE_LIMIT)
188 + .map((variation) => ({
189 + key: variation.title,
190 + title: variation.title,
191 + background: themeColor(variation.settings, 'background'),
192 + swatches: themeSwatches(variation.settings),
193 + previewCss: variation.css,
194 + duotone: variation.settings?.color?.duotone?.theme,
195 + data: { variation },
196 + }));
197 +
198 +const themeColor = (settings, colorName) =>
199 + settings?.color?.palette?.theme?.find((item) => item.slug === colorName)
123 200 ?.color;
124 -};
125 201
126 -const getColors = (settings) => {
127 - return settings?.color?.palette?.theme
202 +const themeSwatches = (settings) =>
203 + settings?.color?.palette?.theme
128 204 ?.filter((item) => item.slug !== 'background')
129 - ?.reduce((acc, item) => {
130 - acc.push(item.color);
131 - return acc;
132 - }, []);
133 -};
205 + ?.map((item) => item.color) ?? [];