Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/sanity/src/presentation/i18n/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,18 @@ export default {
'preview-frame.viewport-button.tooltip_full': 'Switch to full viewport',
/** The viewport size button tooltip text when switching to a narrow viewport */
'preview-frame.viewport-button.tooltip_narrow': 'Switch to narrow viewport',
/** The `aria-label` for the variant button */
'preview-frame.variant-button.aria-label': 'Open variants',
/** The tooltip text for the variant button */
'preview-frame.variant-button.tooltip': 'Variants',
/** The title for the variant dialog */
'preview-frame.variant-dialog.title': 'Preview Variants',
/** The body text for the variant dialog */
'preview-frame.variant-dialog.body': 'Configure your preview variants here.',
/** The text shown when no valid parameters are found */
'preview-frame.variant-dialog.no-parameters': 'No valid parameters found.',
/** The placeholder text for parameter selection */
'preview-frame.variant-dialog.select-placeholder': 'Select {{key}}...',
/** The validation error message shown when the preview location input is missing an origin */
'preview-location-input.error_missing-origin': 'URL must start with {{origin}}',
/** The validation error message shown when the preview location input's base path matches that of the studio */
Expand Down
7 changes: 7 additions & 0 deletions packages/sanity/src/presentation/preview/PreviewHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {useId} from '../useId'
import {OpenPreviewButton} from './OpenPreviewButton'
import {type PreviewProps} from './Preview'
import {PreviewLocationInput} from './PreviewLocationInput'
import {PreviewVariantButton} from './PreviewVariantButton'
import {SharePreviewMenu} from './SharePreviewMenu'

/** @public */
Expand Down Expand Up @@ -45,6 +46,10 @@ const PreviewHeaderDefault = (props: Omit<PreviewHeaderProps, 'renderDefault'>)

const {t} = useTranslation(presentationLocaleNamespace)

const handleVariantChange = useCallback((selections: Record<string, string>) => {
console.warn('Variant selections:', selections)
}, [])

const toggleViewportSize = useCallback(
() => setViewport(viewport === 'desktop' ? 'mobile' : 'desktop'),
[setViewport, viewport],
Expand Down Expand Up @@ -239,6 +244,8 @@ const PreviewHeaderDefault = (props: Omit<PreviewHeaderProps, 'renderDefault'>)
tooltipProps={null}
/>
</Tooltip>

<PreviewVariantButton onVariantChange={handleVariantChange} />
</Flex>

{canSharePreviewAccess && (
Expand Down
122 changes: 122 additions & 0 deletions packages/sanity/src/presentation/preview/PreviewVariantButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import {UsersIcon} from '@sanity/icons'
import {Box, Select, Stack, Text} from '@sanity/ui'
import {useState} from 'react'
import {DECISION_PARAMETERS_SCHEMA, useTranslation, useWorkspace} from 'sanity'

import {Button, Dialog, Tooltip} from '../../ui-components'
import {presentationLocaleNamespace} from '../i18n'

interface PreviewVariantButtonProps {
onVariantChange?: (selections: Record<string, string>) => void
}

/** @internal */
export function PreviewVariantButton({
onVariantChange,
}: PreviewVariantButtonProps): React.JSX.Element | null {
const {t} = useTranslation(presentationLocaleNamespace)
const [isDialogOpen, setIsDialogOpen] = useState(false)
const [selections, setSelections] = useState<Record<string, string>>({})
const workspace = useWorkspace()

// Only render if DECISION_PARAMETERS_SCHEMA is configured
const decisionParametersConfig = workspace.__internal.options[DECISION_PARAMETERS_SCHEMA]
if (!decisionParametersConfig) {
return null
}

// Filter and validate that each value is an array of strings
const validParameters = Object.entries(decisionParametersConfig).filter(([key, value]) => {
return Array.isArray(value) && value.every((item) => typeof item === 'string')
}) as Array<[string, string[]]>

const handleSelectionChange = (key: string, value: string) => {
setSelections((prev) => ({
...prev,
[key]: value,
}))
}

const handleSave = () => {
onVariantChange?.(selections)
setIsDialogOpen(false)
}

const handleCancel = () => {
// Clear all selections
setSelections({})
onVariantChange?.({})
setIsDialogOpen(false)
}

return (
<>
<Tooltip
animate
content={<Text size={1}>{t('preview-frame.variant-button.tooltip')}</Text>}
fallbackPlacements={['bottom-start']}
placement="bottom"
portal
>
<Button
data-testid="preview-variant-button"
aria-label={t('preview-frame.variant-button.aria-label')}
icon={UsersIcon}
mode="bleed"
onClick={() => setIsDialogOpen(true)}
tooltipProps={null}
/>
</Tooltip>

{isDialogOpen && (
<Dialog
id="preview-variant-dialog"
onClose={handleCancel}
width={1}
header={t('preview-frame.variant-dialog.title')}
footer={{
cancelButton: {
onClick: handleCancel,
},
confirmButton: {
text: 'View as...',
onClick: handleSave,
},
}}
>
<Stack space={4}>
<Text>{t('preview-frame.variant-dialog.body')}</Text>

{validParameters.length > 0 ? (
<Stack space={3}>
{validParameters.map(([key, options]) => (
<Box key={key}>
<Text size={1} weight="medium" style={{marginBottom: 8}}>
{key}
</Text>
<Select
value={selections[key] || ''}
onChange={(event) => handleSelectionChange(key, event.currentTarget.value)}
placeholder={t('preview-frame.variant-dialog.select-placeholder', {key})}
>
<option value="">
{t('preview-frame.variant-dialog.select-placeholder', {key})}
</option>
{options.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</Select>
</Box>
))}
</Stack>
) : (
<Text muted>{t('preview-frame.variant-dialog.no-parameters')}</Text>
)}
</Stack>
</Dialog>
)}
</>
)
}
Loading