feat(builder): surface hidden section/pricing/social props + box-model rollout
SectionTypePanel (Accordion/Tabs/Testimonials/Countdown/NumberCounter/ CTASection/CallToAction/FeaturesGrid), PricingStylePanel, and SocialStylePanel all gain a Spacing & Border section (margin/padding per-side, border, box-shadow, opacity), an Animation section, and a Visibility section, wired to the shared SpacingControl/BorderControl/ AnimationControl/VisibilityControl. PricingTable's per-card colors (cardBg/textColor/subColor/featColor/ checkColor/btnBg/btnColor) were previously hard-coded literals computed from featuredBg inside toHtml -- promoted to real optional props (each falling back to the exact prior literal when unset) and exposed via ColorPickerField in PricingStylePanel. SocialStylePanel now exposes SocialLinks' iconShape/gap (already-built props with no control), plus Icon's bgColor/bgShape/bgSize/link and StarRating's filledColor/emptyColor, which the panel's generic iconBgColor/starColor checks never matched since those aren't Icon's or StarRating's real prop names. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,16 +1,38 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useEditor } from '@craftjs/core';
|
||||
import { SHADOW_PRESETS } from '../../../constants/presets';
|
||||
import {
|
||||
StylePanelProps,
|
||||
CollapsibleSection,
|
||||
ColorPickerField,
|
||||
SectionLabel,
|
||||
PresetButtonGrid,
|
||||
labelStyle,
|
||||
inputStyle,
|
||||
smallInputStyle,
|
||||
btnActiveStyle,
|
||||
sectionGap,
|
||||
useNodeProp,
|
||||
SpacingControl,
|
||||
BorderControl,
|
||||
BorderValue,
|
||||
buildBorderShorthand,
|
||||
AnimationControl,
|
||||
VisibilityControl,
|
||||
} from './shared';
|
||||
|
||||
/** Parses a border shorthand string ("2px solid #hex") produced by
|
||||
* buildBorderShorthand() back into its parts for round-tripping through
|
||||
* BorderControl. See SectionTypePanel.tsx for the identical helper. */
|
||||
function parseBorderShorthand(v: string | undefined): BorderValue {
|
||||
if (!v || v === 'none') return { width: '', style: 'none', color: '#000000' };
|
||||
const m = String(v).match(/^([\d.]+[a-z%]*)\s+(\w+)\s+(.+)$/);
|
||||
if (!m) return { width: '', style: 'none', color: '#000000' };
|
||||
return { width: m[1], style: m[2], color: m[3] };
|
||||
}
|
||||
|
||||
const capSide = (s: string): string => s.charAt(0).toUpperCase() + s.slice(1);
|
||||
|
||||
const bulletOptions = [
|
||||
{ label: '✓', value: 'check' },
|
||||
{ label: '●', value: 'dot' },
|
||||
@@ -26,10 +48,12 @@ const bulletChar: Record<string, string> = {
|
||||
|
||||
export const PricingStylePanel: React.FC<StylePanelProps> = ({ selectedId, nodeProps }) => {
|
||||
const { actions } = useEditor();
|
||||
const { setProp, setPropStyle } = useNodeProp(selectedId);
|
||||
const [expandedPlan, setExpandedPlan] = useState<number>(0);
|
||||
|
||||
const plans: any[] = Array.isArray(nodeProps.plans) ? nodeProps.plans : [];
|
||||
const currentBullet = nodeProps.bulletType || 'check';
|
||||
const style = nodeProps.style || {};
|
||||
|
||||
const updatePlan = useCallback((planIndex: number, field: string, value: any) => {
|
||||
actions.setProp(selectedId, (props: any) => {
|
||||
@@ -204,6 +228,79 @@ export const PricingStylePanel: React.FC<StylePanelProps> = ({ selectedId, nodeP
|
||||
{/* Colors */}
|
||||
<CollapsibleSection title="Colors" defaultOpen={false}>
|
||||
<ColorPickerField label="Featured Plan Color" value={nodeProps.featuredBg || '#3b82f6'} onChange={(v) => actions.setProp(selectedId, (p: any) => { p.featuredBg = v; })} />
|
||||
{/* Regular (non-featured) card colors -- built into PricingTable's
|
||||
render/toHtml but previously hard-coded literals with no control
|
||||
surfacing them. Each falls back to the prior literal when blank. */}
|
||||
<ColorPickerField label="Card Background" value={nodeProps.cardBg || '#ffffff'} onChange={(v) => setProp('cardBg', v)} />
|
||||
<ColorPickerField label="Heading / Price Color" value={nodeProps.textColor || '#18181b'} onChange={(v) => setProp('textColor', v)} />
|
||||
<ColorPickerField label="Period Text Color" value={nodeProps.subColor || '#64748b'} onChange={(v) => setProp('subColor', v)} />
|
||||
<ColorPickerField label="Feature Text Color" value={nodeProps.featColor || '#4b5563'} onChange={(v) => setProp('featColor', v)} />
|
||||
<ColorPickerField label="Checkmark Color" value={nodeProps.checkColor || '#10b981'} onChange={(v) => setProp('checkColor', v)} />
|
||||
<ColorPickerField label="Button Background" value={nodeProps.btnBg || nodeProps.featuredBg || '#3b82f6'} onChange={(v) => setProp('btnBg', v)} />
|
||||
<ColorPickerField label="Button Text Color" value={nodeProps.btnColor || '#ffffff'} onChange={(v) => setProp('btnColor', v)} />
|
||||
</CollapsibleSection>
|
||||
|
||||
{/* Box model: margin, padding, border, shadow, opacity */}
|
||||
<CollapsibleSection title="Spacing & Border" defaultOpen={false}>
|
||||
<SpacingControl
|
||||
label="Margin"
|
||||
value={{
|
||||
top: style.marginTop as string, right: style.marginRight as string,
|
||||
bottom: style.marginBottom as string, left: style.marginLeft as string,
|
||||
}}
|
||||
onChange={(side, v) => setPropStyle(`margin${capSide(side)}`, v)}
|
||||
/>
|
||||
<SpacingControl
|
||||
label="Padding"
|
||||
value={{
|
||||
top: style.paddingTop as string, right: style.paddingRight as string,
|
||||
bottom: style.paddingBottom as string, left: style.paddingLeft as string,
|
||||
}}
|
||||
onChange={(side, v) => setPropStyle(`padding${capSide(side)}`, v)}
|
||||
/>
|
||||
<BorderControl
|
||||
value={parseBorderShorthand(style.border as string)}
|
||||
onChange={(v) => setPropStyle('border', buildBorderShorthand(v))}
|
||||
/>
|
||||
<div className="guided-section">
|
||||
<SectionLabel>Shadow</SectionLabel>
|
||||
<PresetButtonGrid presets={SHADOW_PRESETS} activeValue={style.boxShadow as string} onSelect={(v) => setPropStyle('boxShadow', v)} />
|
||||
</div>
|
||||
<div className="guided-section">
|
||||
<SectionLabel>Opacity</SectionLabel>
|
||||
<input
|
||||
type="range"
|
||||
min={0}
|
||||
max={100}
|
||||
value={style.opacity !== undefined ? Math.round(Number(style.opacity) * 100) : 100}
|
||||
onChange={(e) => setPropStyle('opacity', String(Number(e.target.value) / 100))}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
|
||||
{/* Entrance animation */}
|
||||
<CollapsibleSection title="Animation" defaultOpen={false}>
|
||||
<AnimationControl
|
||||
value={{ animation: nodeProps.animation || 'none', animationDelay: nodeProps.animationDelay || '0' }}
|
||||
onChange={(v) => { setProp('animation', v.animation); setProp('animationDelay', v.animationDelay); }}
|
||||
/>
|
||||
</CollapsibleSection>
|
||||
|
||||
{/* Responsive visibility */}
|
||||
<CollapsibleSection title="Visibility" defaultOpen={false}>
|
||||
<VisibilityControl
|
||||
value={{
|
||||
hideOnDesktop: !!nodeProps.hideOnDesktop,
|
||||
hideOnTablet: !!nodeProps.hideOnTablet,
|
||||
hideOnMobile: !!nodeProps.hideOnMobile,
|
||||
}}
|
||||
onChange={(v) => {
|
||||
setProp('hideOnDesktop', !!v.hideOnDesktop);
|
||||
setProp('hideOnTablet', !!v.hideOnTablet);
|
||||
setProp('hideOnMobile', !!v.hideOnMobile);
|
||||
}}
|
||||
/>
|
||||
</CollapsibleSection>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
BG_COLORS,
|
||||
SPACING_PRESETS,
|
||||
RADIUS_PRESETS,
|
||||
SHADOW_PRESETS,
|
||||
} from '../../../constants/presets';
|
||||
import {
|
||||
StylePanelProps,
|
||||
@@ -15,10 +16,31 @@ import {
|
||||
inputStyle,
|
||||
sectionGap,
|
||||
useNodeProp,
|
||||
SpacingControl,
|
||||
BorderControl,
|
||||
BorderValue,
|
||||
buildBorderShorthand,
|
||||
AnimationControl,
|
||||
VisibilityControl,
|
||||
} from './shared';
|
||||
import { FeaturesEditor } from './FeaturesEditor';
|
||||
import { ArrayItemFieldsEditor } from './ArrayItemFields';
|
||||
|
||||
/** Parses a border shorthand string ("2px solid #hex") produced by
|
||||
* buildBorderShorthand() back into its parts for round-tripping through
|
||||
* BorderControl. Anything that doesn't match (undefined/'none'/legacy
|
||||
* hand-authored values) falls back to an empty/none border rather than
|
||||
* throwing, since this is display-only -- the next edit always re-emits a
|
||||
* clean shorthand via buildBorderShorthand(). */
|
||||
function parseBorderShorthand(v: string | undefined): BorderValue {
|
||||
if (!v || v === 'none') return { width: '', style: 'none', color: '#000000' };
|
||||
const m = String(v).match(/^([\d.]+[a-z%]*)\s+(\w+)\s+(.+)$/);
|
||||
if (!m) return { width: '', style: 'none', color: '#000000' };
|
||||
return { width: m[1], style: m[2], color: m[3] };
|
||||
}
|
||||
|
||||
const capSide = (s: string): string => s.charAt(0).toUpperCase() + s.slice(1);
|
||||
|
||||
/* ---------- SECTION-TYPE (Accordion, Tabs, Pricing, Testimonials, etc.) ---------- */
|
||||
export const SectionTypePanel: React.FC<StylePanelProps & { typeName: string }> = ({ selectedId, nodeProps, typeName }) => {
|
||||
const { setProp, setPropStyle } = useNodeProp(selectedId);
|
||||
@@ -26,7 +48,12 @@ export const SectionTypePanel: React.FC<StylePanelProps & { typeName: string }>
|
||||
const style = nodeProps.style || {};
|
||||
|
||||
// Find all string/number/boolean props
|
||||
const SKIP_PROPS = new Set(['style', 'children', 'cssId', 'cssClass']);
|
||||
const SKIP_PROPS = new Set([
|
||||
'style', 'children', 'cssId', 'cssClass',
|
||||
// Rendered via dedicated controls below (Animation/Visibility), not the
|
||||
// generic Content field dump -- otherwise they'd show up twice.
|
||||
'animation', 'animationDelay', 'hideOnDesktop', 'hideOnTablet', 'hideOnMobile',
|
||||
]);
|
||||
const scalarProps = Object.entries(nodeProps).filter(
|
||||
([key, val]) => !SKIP_PROPS.has(key) && (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean')
|
||||
);
|
||||
@@ -126,6 +153,69 @@ export const SectionTypePanel: React.FC<StylePanelProps & { typeName: string }>
|
||||
<PresetButtonGrid presets={RADIUS_PRESETS} activeValue={style.borderRadius as string} onSelect={(v) => setPropStyle('borderRadius', v)} />
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
|
||||
{/* Box model: margin, padding, border, shadow, opacity */}
|
||||
<CollapsibleSection title="Spacing & Border" defaultOpen={false}>
|
||||
<SpacingControl
|
||||
label="Margin"
|
||||
value={{
|
||||
top: style.marginTop as string, right: style.marginRight as string,
|
||||
bottom: style.marginBottom as string, left: style.marginLeft as string,
|
||||
}}
|
||||
onChange={(side, v) => setPropStyle(`margin${capSide(side)}`, v)}
|
||||
/>
|
||||
<SpacingControl
|
||||
label="Padding"
|
||||
value={{
|
||||
top: style.paddingTop as string, right: style.paddingRight as string,
|
||||
bottom: style.paddingBottom as string, left: style.paddingLeft as string,
|
||||
}}
|
||||
onChange={(side, v) => setPropStyle(`padding${capSide(side)}`, v)}
|
||||
/>
|
||||
<BorderControl
|
||||
value={parseBorderShorthand(style.border as string)}
|
||||
onChange={(v) => setPropStyle('border', buildBorderShorthand(v))}
|
||||
/>
|
||||
<div className="guided-section">
|
||||
<SectionLabel>Shadow</SectionLabel>
|
||||
<PresetButtonGrid presets={SHADOW_PRESETS} activeValue={style.boxShadow as string} onSelect={(v) => setPropStyle('boxShadow', v)} />
|
||||
</div>
|
||||
<div className="guided-section">
|
||||
<SectionLabel>Opacity</SectionLabel>
|
||||
<input
|
||||
type="range"
|
||||
min={0}
|
||||
max={100}
|
||||
value={style.opacity !== undefined ? Math.round(Number(style.opacity) * 100) : 100}
|
||||
onChange={(e) => setPropStyle('opacity', String(Number(e.target.value) / 100))}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
|
||||
{/* Entrance animation */}
|
||||
<CollapsibleSection title="Animation" defaultOpen={false}>
|
||||
<AnimationControl
|
||||
value={{ animation: nodeProps.animation || 'none', animationDelay: nodeProps.animationDelay || '0' }}
|
||||
onChange={(v) => { setProp('animation', v.animation); setProp('animationDelay', v.animationDelay); }}
|
||||
/>
|
||||
</CollapsibleSection>
|
||||
|
||||
{/* Responsive visibility */}
|
||||
<CollapsibleSection title="Visibility" defaultOpen={false}>
|
||||
<VisibilityControl
|
||||
value={{
|
||||
hideOnDesktop: !!nodeProps.hideOnDesktop,
|
||||
hideOnTablet: !!nodeProps.hideOnTablet,
|
||||
hideOnMobile: !!nodeProps.hideOnMobile,
|
||||
}}
|
||||
onChange={(v) => {
|
||||
setProp('hideOnDesktop', !!v.hideOnDesktop);
|
||||
setProp('hideOnTablet', !!v.hideOnTablet);
|
||||
setProp('hideOnMobile', !!v.hideOnMobile);
|
||||
}}
|
||||
/>
|
||||
</CollapsibleSection>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useEditor } from '@craftjs/core';
|
||||
import {
|
||||
BG_COLORS,
|
||||
SPACING_PRESETS,
|
||||
SHADOW_PRESETS,
|
||||
} from '../../../constants/presets';
|
||||
import {
|
||||
StylePanelProps,
|
||||
@@ -17,8 +18,33 @@ import {
|
||||
btnActiveStyle,
|
||||
sectionGap,
|
||||
useNodeProp,
|
||||
SpacingControl,
|
||||
BorderControl,
|
||||
BorderValue,
|
||||
buildBorderShorthand,
|
||||
AnimationControl,
|
||||
VisibilityControl,
|
||||
} from './shared';
|
||||
|
||||
/** Parses a border shorthand string ("2px solid #hex") produced by
|
||||
* buildBorderShorthand() back into its parts for round-tripping through
|
||||
* BorderControl. See SectionTypePanel.tsx for the identical helper. */
|
||||
function parseBorderShorthand(v: string | undefined): BorderValue {
|
||||
if (!v || v === 'none') return { width: '', style: 'none', color: '#000000' };
|
||||
const m = String(v).match(/^([\d.]+[a-z%]*)\s+(\w+)\s+(.+)$/);
|
||||
if (!m) return { width: '', style: 'none', color: '#000000' };
|
||||
return { width: m[1], style: m[2], color: m[3] };
|
||||
}
|
||||
|
||||
const capSide = (s: string): string => s.charAt(0).toUpperCase() + s.slice(1);
|
||||
|
||||
const ICON_SHAPES = [
|
||||
{ label: 'None', value: 'none' },
|
||||
{ label: 'Circle', value: 'circle' },
|
||||
{ label: 'Square', value: 'square' },
|
||||
{ label: 'Rounded', value: 'rounded' },
|
||||
];
|
||||
|
||||
/* ---------- SOCIAL / ICON / STAR RATING ---------- */
|
||||
export const SocialStylePanel: React.FC<StylePanelProps> = ({ selectedId, nodeProps }) => {
|
||||
const { actions } = useEditor();
|
||||
@@ -111,6 +137,72 @@ export const SocialStylePanel: React.FC<StylePanelProps> = ({ selectedId, nodePr
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Icon shape (SocialLinks) -- exists on the component (iconShape prop
|
||||
drives the circle/square/rounded background box) but previously had
|
||||
no control. */}
|
||||
{nodeProps.iconShape !== undefined && (
|
||||
<div style={sectionGap}>
|
||||
<label style={labelStyle}>Icon Shape</label>
|
||||
<div style={{ display: 'flex', gap: 4 }}>
|
||||
{ICON_SHAPES.map((s) => (
|
||||
<button key={s.value} onClick={() => setProp('iconShape', s.value)} style={{ ...btnActiveStyle(nodeProps.iconShape === s.value), flex: 1 }}>
|
||||
{s.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* Gap between links (SocialLinks) -- exists on the component but
|
||||
previously had no control. */}
|
||||
{nodeProps.gap !== undefined && (
|
||||
<div style={sectionGap}>
|
||||
<label style={labelStyle}>Gap</label>
|
||||
<input type="text" value={nodeProps.gap || '10px'} onChange={(e) => setProp('gap', e.target.value)} placeholder="10px" style={inputStyle} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Background shape / size / link (Icon component) -- bgColor, bgShape,
|
||||
bgSize, link all already exist on Icon.craft.props but had no
|
||||
control; the generic `color`/`iconColor` checks above never matched
|
||||
Icon's actual prop names. */}
|
||||
{nodeProps.bgShape !== undefined && (
|
||||
<div style={sectionGap}>
|
||||
<label style={labelStyle}>Background Shape</label>
|
||||
<div style={{ display: 'flex', gap: 4 }}>
|
||||
{ICON_SHAPES.map((s) => (
|
||||
<button key={s.value} onClick={() => setProp('bgShape', s.value)} style={{ ...btnActiveStyle(nodeProps.bgShape === s.value), flex: 1 }}>
|
||||
{s.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{nodeProps.bgColor !== undefined && (
|
||||
<ColorPickerField label="Background Color" value={nodeProps.bgColor === 'transparent' ? '' : nodeProps.bgColor} onChange={(v) => setProp('bgColor', v)} />
|
||||
)}
|
||||
{nodeProps.bgSize !== undefined && (
|
||||
<div style={sectionGap}>
|
||||
<label style={labelStyle}>Background Size</label>
|
||||
<input type="text" value={nodeProps.bgSize || '56px'} onChange={(e) => setProp('bgSize', e.target.value)} placeholder="56px" style={inputStyle} />
|
||||
</div>
|
||||
)}
|
||||
{nodeProps.link !== undefined && (
|
||||
<div style={sectionGap}>
|
||||
<label style={labelStyle}>Link URL</label>
|
||||
<input type="text" value={nodeProps.link || ''} onChange={(e) => setProp('link', e.target.value)} placeholder="https://..." style={inputStyle} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Star colors (StarRating) -- exist on the component as filledColor /
|
||||
emptyColor but had no control (the generic `starColor` check above
|
||||
never matched StarRating's actual prop names). */}
|
||||
{nodeProps.filledColor !== undefined && (
|
||||
<ColorPickerField label="Filled Star Color" value={nodeProps.filledColor || '#f59e0b'} onChange={(v) => setProp('filledColor', v)} />
|
||||
)}
|
||||
{nodeProps.emptyColor !== undefined && (
|
||||
<ColorPickerField label="Empty Star Color" value={nodeProps.emptyColor || '#d1d5db'} onChange={(v) => setProp('emptyColor', v)} />
|
||||
)}
|
||||
|
||||
{/* Colors */}
|
||||
{nodeProps.iconColor !== undefined && (
|
||||
<ColorPickerField label="Icon Color" value={nodeProps.iconColor || '#3b82f6'} onChange={(v) => setProp('iconColor', v)} />
|
||||
@@ -164,6 +256,69 @@ export const SocialStylePanel: React.FC<StylePanelProps> = ({ selectedId, nodePr
|
||||
<PresetButtonGrid presets={SPACING_PRESETS} activeValue={style.padding as string} onSelect={(v) => setPropStyle('padding', v)} />
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
|
||||
{/* Box model: margin, padding (per-side), border, shadow, opacity */}
|
||||
<CollapsibleSection title="Spacing & Border" defaultOpen={false}>
|
||||
<SpacingControl
|
||||
label="Margin"
|
||||
value={{
|
||||
top: style.marginTop as string, right: style.marginRight as string,
|
||||
bottom: style.marginBottom as string, left: style.marginLeft as string,
|
||||
}}
|
||||
onChange={(side, v) => setPropStyle(`margin${capSide(side)}`, v)}
|
||||
/>
|
||||
<SpacingControl
|
||||
label="Padding"
|
||||
value={{
|
||||
top: style.paddingTop as string, right: style.paddingRight as string,
|
||||
bottom: style.paddingBottom as string, left: style.paddingLeft as string,
|
||||
}}
|
||||
onChange={(side, v) => setPropStyle(`padding${capSide(side)}`, v)}
|
||||
/>
|
||||
<BorderControl
|
||||
value={parseBorderShorthand(style.border as string)}
|
||||
onChange={(v) => setPropStyle('border', buildBorderShorthand(v))}
|
||||
/>
|
||||
<div className="guided-section">
|
||||
<SectionLabel>Shadow</SectionLabel>
|
||||
<PresetButtonGrid presets={SHADOW_PRESETS} activeValue={style.boxShadow as string} onSelect={(v) => setPropStyle('boxShadow', v)} />
|
||||
</div>
|
||||
<div className="guided-section">
|
||||
<SectionLabel>Opacity</SectionLabel>
|
||||
<input
|
||||
type="range"
|
||||
min={0}
|
||||
max={100}
|
||||
value={style.opacity !== undefined ? Math.round(Number(style.opacity) * 100) : 100}
|
||||
onChange={(e) => setPropStyle('opacity', String(Number(e.target.value) / 100))}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
|
||||
{/* Entrance animation */}
|
||||
<CollapsibleSection title="Animation" defaultOpen={false}>
|
||||
<AnimationControl
|
||||
value={{ animation: nodeProps.animation || 'none', animationDelay: nodeProps.animationDelay || '0' }}
|
||||
onChange={(v) => { setProp('animation', v.animation); setProp('animationDelay', v.animationDelay); }}
|
||||
/>
|
||||
</CollapsibleSection>
|
||||
|
||||
{/* Responsive visibility */}
|
||||
<CollapsibleSection title="Visibility" defaultOpen={false}>
|
||||
<VisibilityControl
|
||||
value={{
|
||||
hideOnDesktop: !!nodeProps.hideOnDesktop,
|
||||
hideOnTablet: !!nodeProps.hideOnTablet,
|
||||
hideOnMobile: !!nodeProps.hideOnMobile,
|
||||
}}
|
||||
onChange={(v) => {
|
||||
setProp('hideOnDesktop', !!v.hideOnDesktop);
|
||||
setProp('hideOnTablet', !!v.hideOnTablet);
|
||||
setProp('hideOnMobile', !!v.hideOnMobile);
|
||||
}}
|
||||
/>
|
||||
</CollapsibleSection>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user