import React from "react"; import { HighlightableField } from "../components/HighlightableField"; import { RangeSlider } from "../components/RangeSlider"; import { Toggle } from "../components/Toggle"; import { LayoutPreviewIcon, SelectorStylePreviewIcon, ShapePreviewIcon, } from "../components/swatchPreviewIcons"; import { ChoiceGroup, ChoiceOption } from "../components/ChoiceGroup"; import { coerceInt } from "../constants"; import { EXTENSION_LAYOUT_ALLOWED_STYLES, EXTENSION_LAYOUT_SHOWS_SHAPE, } from "./presentationConstants"; import type { BaseTabProps } from "../tabs/types"; import type { ExtensionLayout as Layout, ExtensionSelectorStyle as SelectorStyle, ExtensionShape as Shape, } from "./presentationTypes"; type GeneralExtrasSection = "layout" | "size" | "options"; // Mobile layout cards (no Flow — Flow is desktop-only). const MOBILE_LAYOUT_OPTIONS = [ { value: "wrap", label: "Wrap", desc: "Wraps to new lines" }, { value: "grid", label: "Grid", desc: "Fixed columns" }, { value: "pill", label: "Pill", desc: "Connected pills" }, { value: "slider", label: "Slider", desc: "Slide selector" }, ] as const; // Selector styles available for mobile overrides. export const SELECTOR_STYLE_OPTIONS = [ { value: "underline", label: "Underline", desc: "Line below selected" }, { value: "outline", label: "Outline", desc: "Border ring on selected" }, { value: "background", label: "Background", desc: "Highlight selected" }, ] as const; // Mobile shape cards (icon + label only — no description, tighter padding). const MOBILE_SHAPE_OPTIONS = [ { value: "square", label: "Square" }, { value: "soft", label: "Soft" }, { value: "rounded", label: "Rounded" }, { value: "squircle", label: "Squircle" }, { value: "circle", label: "Circle" }, { value: "diamond", label: "Diamond" }, { value: "hexagon", label: "Hexagon" }, ] as const; interface GeneralExtrasSlotProps extends BaseTabProps { slot: GeneralExtrasSection; showToast: (payload: { type: "success" | "error" | "info"; message: string; durationMs?: number; }) => void; } export function GeneralTabExtras({ slot, settings, setSettings, showToast, }: GeneralExtrasSlotProps): React.ReactElement | null { const extensionSettings = settings as typeof settings & { layout?: Layout; swatch_style?: SelectorStyle; shape?: Shape; mobile_layout?: Layout | "wrap" | "grid" | "pill" | "slider"; mobile_swatch_style?: SelectorStyle | ""; mobile_shape?: Shape; }; const swatchWidth = coerceInt(extensionSettings.swatch_width, 32); const swatchHeight = coerceInt(extensionSettings.swatch_height, 32); const layout = (extensionSettings.layout || "wrap") as Layout; const storedStyle = (extensionSettings.swatch_style || "underline") as SelectorStyle; const mobileLayout = (extensionSettings.mobile_layout || "wrap") as Layout | "wrap"; const mobileAllowedStyles = (EXTENSION_LAYOUT_ALLOWED_STYLES[ mobileLayout ] || ["underline"]) as SelectorStyle[]; const showMobileSelectorStyle = mobileAllowedStyles.length > 0 && mobileLayout !== "slider"; const mobileSelectorCandidate = (extensionSettings.mobile_swatch_style || storedStyle || "underline") as SelectorStyle; const mobileSelectorStyle: SelectorStyle = mobileAllowedStyles.includes( mobileSelectorCandidate, ) ? mobileSelectorCandidate : mobileAllowedStyles[0] || "underline"; const currentMobileShape = (extensionSettings.mobile_shape || "square") as Shape; const buttonCustomSizing = !!extensionSettings.button_custom_sizing; const buttonSwatchWidth = coerceInt( extensionSettings.button_swatch_width ?? swatchWidth, swatchWidth, ); const buttonSwatchHeight = coerceInt( extensionSettings.button_swatch_height ?? swatchHeight, swatchHeight, ); if (slot === "layout") { // Derive each dynamic radiogroup's rendered option set ONCE, so the // `optionValues` passed to ChoiceGroup and the `ChoiceOption` children // mapped below always match exactly (roving/Arrow order stays correct). const mobileSelectorOptions = SELECTOR_STYLE_OPTIONS.filter((o) => mobileAllowedStyles.includes(o.value), ); const mobileShapeOptions = MOBILE_SHAPE_OPTIONS.filter((shapeOption) => { if ( mobileLayout === "slider" && (shapeOption.value === "diamond" || shapeOption.value === "hexagon") ) { return false; } return true; }); return ( <> {layout === "slider" ? (
setSettings({ ...settings, slider_selector_padding: val }) } />
) : null}
{ if (layout === "flow" && !checked) { showToast({ type: "info", message: "Switch off Flow layout to remove different layout for mobile", durationMs: 3500, }); return; } setSettings({ ...settings, use_mobile_layout: checked }); }} />
{settings.use_mobile_layout ? (
setSettings({ ...settings, mobile_cutoff: val }) } />
o.value)} onChange={(next) => { const nextLayout = next as Layout; const nextAllowed = EXTENSION_LAYOUT_ALLOWED_STYLES[nextLayout]; const nextStyle = nextAllowed.length > 0 && nextAllowed.includes(mobileSelectorCandidate) ? mobileSelectorCandidate : nextAllowed[0] || "underline"; setSettings({ ...settings, mobile_layout: nextLayout, mobile_swatch_style: nextAllowed.length > 0 ? nextStyle : undefined, }); }} className="b3-wvs-choice-grid b3-wvs-choice-grid--layout" > {MOBILE_LAYOUT_OPTIONS.map((layoutOption) => (
{layoutOption.label}
{layoutOption.desc}
))}
{mobileLayout === "grid" ? (
setSettings({ ...settings, mobile_grid_max_columns: val }) } />
) : null} {showMobileSelectorStyle ? (
o.value)} onChange={(next) => setSettings({ ...settings, mobile_swatch_style: next as SelectorStyle, }) } className="b3-wvs-choice-grid b3-wvs-choice-grid--selector" > {mobileSelectorOptions.map((styleOption) => (
{styleOption.label}
{styleOption.desc}
))}
{mobileSelectorStyle === "outline" ? (
setSettings({ ...settings, outline_border_width: val, }) } />
) : null}
) : null} {EXTENSION_LAYOUT_SHOWS_SHAPE[mobileLayout] ? (
o.value)} onChange={(next) => setSettings({ ...settings, mobile_shape: next as Shape, }) } className="b3-wvs-choice-grid b3-wvs-choice-grid--shape" > {mobileShapeOptions.map((shapeOption) => (
{shapeOption.label}
))}
) : null} {(settings.mobile_layout ?? "wrap") !== "pill" ? (
setSettings({ ...settings, mobile_swatch_gap: val }) } />
) : null}
) : null} ); } if (slot === "size") { return ( <> setSettings({ ...settings, button_custom_sizing: checked, }) } /> {buttonCustomSizing ? (
setSettings({ ...settings, button_swatch_width: val }) } /> setSettings({ ...settings, button_swatch_height: val }) } />
) : null} ); } if (slot === "options") { return ( <> setSettings({ ...settings, hide_clear_button: !checked }) } label="Show Clear Button" description='Show the WooCommerce "Clear" link (reset variations)' /> setSettings({ ...settings, clear_on_reselect: checked }) } label="Clear on Reselect" description='Click the currently-selected swatch again to unselect it (sets the attribute back to "Choose an option")' /> ); } return null; }