import { useEffect, useMemo, useState } from 'react';
import { Box } from '@chakra-ui/react';
import { CodeTab, PreviewTab, TabsLayout } from '../../components/common/TabsLayout';
import useComponentProps from '../../hooks/useComponentProps';
import { ComponentPropsProvider } from '../../components/context/ComponentPropsContext';
import { useColorModeValue } from '../../components/setup/color-mode';
import CodeExample from '../../components/code/CodeExample';
import Dependencies from '../../components/code/Dependencies';
import PropTable from '../../components/common/Preview/PropTable';
import Customize from '../../components/common/Preview/Customize';
import PreviewSlider from '../../components/common/Preview/PreviewSlider';
import PreviewSwitch from '../../components/common/Preview/PreviewSwitch';
import PreviewSelect from '../../components/common/Preview/PreviewSelect';
import PreviewInput from '../../components/common/Preview/PreviewInput';
import PreviewColorPickerCustom from '../../components/common/Preview/PreviewColorPickerCustom';
import BellToggle from '../../content/Micro/BellToggle/BellToggle';
import { bellToggle } from '../../constants/code/Micro/bellToggleCode';
const DEFAULT_PROPS = {
offLabel: 'Notify me',
onLabel: "You'll be notified",
color: '#f5f5f5',
background: '#27272a',
onColor: '#18181b',
onBackground: '#f5f5f5',
size: 'md',
radius: 22,
ringAmplitude: 17,
ringPasses: 5,
ringDecay: 1,
ringDuration: 820,
ringPivot: 16,
crossfadeMs: 200,
revealBounce: 0,
badge: true,
badgeColor: '#ef4444',
waves: true,
clapper: false,
disabled: false
};
const SIZE_OPTIONS = [
{ value: 'sm', label: 'Small' },
{ value: 'md', label: 'Medium' },
{ value: 'lg', label: 'Large' }
];
const BellToggleDemo = () => {
const { props, updateProp, resetProps, hasChanges } = useComponentProps(DEFAULT_PROPS);
const {
offLabel,
onLabel,
color,
background,
onColor,
onBackground,
size,
radius,
ringAmplitude,
ringPasses,
ringDecay,
ringDuration,
ringPivot,
crossfadeMs,
revealBounce,
badge,
badgeColor,
waves,
clapper,
disabled
} = props;
const [on, setOn] = useState(false);
const [count, setCount] = useState(0);
useEffect(() => {
if (!on) {
setCount(0);
return undefined;
}
let timer;
const next = () => {
timer = setTimeout(
() => {
setCount(c => Math.min(9, c + 1));
next();
},
1100 + Math.random() * 1400
);
};
next();
return () => clearTimeout(timer);
}, [on]);
const renderedColor = useColorModeValue(color === DEFAULT_PROPS.color ? '#18181b' : color, color);
const renderedBackground = useColorModeValue(
background === DEFAULT_PROPS.background ? '#f6f6f6' : background,
background
);
const renderedOnColor = useColorModeValue(onColor === DEFAULT_PROPS.onColor ? '#ffffff' : onColor, onColor);
const renderedOnBackground = useColorModeValue(
onBackground === DEFAULT_PROPS.onBackground ? '#18181b' : onBackground,
onBackground
);
const propData = useMemo(
() => [
{
name: 'offLabel',
type: 'string',
default: '"Notify me"',
description: 'The face at rest. Its width sets how far the pill is clipped.'
},
{
name: 'onLabel',
type: 'string',
default: '"You\'ll be notified"',
description:
'The face after the yes press. The pill reserves the width of the longer label; only the visible part changes, so neighbours never shift.'
},
{
name: 'icon',
type: 'ReactNode',
default: 'undefined',
description: 'Replaces the bell. Adjust ringPivot for a glyph that hangs elsewhere.'
},
{
name: 'label',
type: 'string',
default: 'undefined',
description: 'Constant accessible name. Falls back to offLabel.'
},
{ name: 'color', type: 'string', default: '"#f5f5f5"', description: 'Ink at rest: label, bell and hover tint.' },
{ name: 'background', type: 'string', default: '"#27272a"', description: 'Pill fill at rest.' },
{ name: 'onColor', type: 'string', default: '"#18181b"', description: 'Ink when pressed.' },
{ name: 'onBackground', type: 'string', default: '"#f5f5f5"', description: 'Fill when pressed.' },
{
name: 'size',
type: '"sm" | "md" | "lg"',
default: '"md"',
description: '36, 44 or 52 pixels tall with matching type, icon and padding.'
},
{ name: 'radius', type: 'number', default: '22', description: 'Corner radius of the pill and of the clip cap.' },
{
name: 'ringAmplitude',
type: 'number',
default: '17',
description: 'Degrees of the first swing. Every later swing scales from it.'
},
{
name: 'ringPasses',
type: 'number',
default: '5',
description: 'Half-swings before rest. 2 is a nod, 9 a peal.'
},
{
name: 'ringDecay',
type: 'number',
default: '1',
description: 'How fast the swings die. 1 evenly, 2 the second is already small, 0.5 keeps shaking.'
},
{ name: 'ringDuration', type: 'number', default: '820', description: 'Milliseconds for the whole ring.' },
{
name: 'ringPivot',
type: 'number',
default: '16',
description: 'Where the icon hangs from, as a percentage of its height. 16 is the crown, 50 the centre.'
},
{
name: 'crossfadeMs',
type: 'number',
default: '200',
description: 'The label and fill crossfade. Also all a keyboard toggle gets.'
},
{
name: 'revealBounce',
type: 'number',
default: '0',
description: 'Overshoot of the unfurl. 0 is critically damped; 0.2 lets the cap pass its mark and return.'
},
{
name: 'count',
type: 'number',
default: '0',
description: 'Notifications waiting. A rise while on rolls the badge and wobbles the bell.'
},
{ name: 'badge', type: 'boolean', default: 'true', description: 'Show the count on the bell while on.' },
{ name: 'badgeColor', type: 'string', default: '"#ef4444"', description: 'The badge.' },
{ name: 'badgeTextColor', type: 'string', default: '"#ffffff"', description: 'The count on the badge.' },
{ name: 'waves', type: 'boolean', default: 'true', description: 'Sound waves leave the rim on every swing.' },
{
name: 'clapper',
type: 'boolean',
default: 'false',
description: 'Draw a bell with a clapper that swings a beat behind the body.'
},
{
name: 'pressed',
type: 'boolean',
default: 'undefined',
description: 'Controlled state. Outside changes only crossfade.'
},
{ name: 'defaultPressed', type: 'boolean', default: 'false', description: 'Initial state when uncontrolled.' },
{ name: 'onChange', type: '(pressed: boolean) => void', default: '-', description: 'Called on every toggle.' },
{
name: 'disabled',
type: 'boolean',
default: 'false',
description: 'Dims the pill and ignores input. The state is kept.'
},
{ name: 'className', type: 'string', default: '""', description: 'Extra classes for the root.' }
],
[]
);
return (
<ComponentPropsProvider props={props} defaultProps={DEFAULT_PROPS} resetProps={resetProps} hasChanges={hasChanges}>
<TabsLayout>
<PreviewTab>
<Box
position="relative"
className="demo-container"
h={400}
overflow="hidden"
display="flex"
alignItems="center"
justifyContent="center"
>
<BellToggle
offLabel={offLabel}
onLabel={onLabel}
color={renderedColor}
background={renderedBackground}
onColor={renderedOnColor}
onBackground={renderedOnBackground}
size={size}
radius={radius}
ringAmplitude={ringAmplitude}
ringPasses={ringPasses}
ringDecay={ringDecay}
ringDuration={ringDuration}
ringPivot={ringPivot}
crossfadeMs={crossfadeMs}
revealBounce={revealBounce}
count={count}
badge={badge}
badgeColor={badgeColor}
waves={waves}
clapper={clapper}
pressed={on}
onChange={setOn}
disabled={disabled}
/>
</Box>
<Customize>
<PreviewColorPickerCustom title="Text" color={renderedColor} onChange={val => updateProp('color', val)} />
<PreviewColorPickerCustom
title="Background"
color={renderedBackground}
onChange={val => updateProp('background', val)}
/>
<PreviewColorPickerCustom
title="Pressed Text"
color={renderedOnColor}
onChange={val => updateProp('onColor', val)}
/>
<PreviewColorPickerCustom
title="Pressed Background"
color={renderedOnBackground}
onChange={val => updateProp('onBackground', val)}
/>
<PreviewSelect
title="Size"
options={SIZE_OPTIONS}
value={size}
onChange={val => updateProp('size', val)}
width={120}
/>
<PreviewSlider
title="Radius"
min={0}
max={26}
step={1}
value={radius}
valueUnit="px"
onChange={val => updateProp('radius', val)}
/>
<PreviewSlider
title="Ring Amplitude"
min={6}
max={40}
step={1}
value={ringAmplitude}
valueUnit="°"
onChange={val => updateProp('ringAmplitude', val)}
/>
<PreviewSlider
title="Ring Passes"
min={2}
max={9}
step={1}
value={ringPasses}
onChange={val => updateProp('ringPasses', val)}
/>
<PreviewSlider
title="Ring Decay"
min={0.5}
max={2}
step={0.1}
value={ringDecay}
onChange={val => updateProp('ringDecay', val)}
/>
<PreviewSlider
title="Ring Duration"
min={400}
max={1400}
step={20}
value={ringDuration}
valueUnit="ms"
onChange={val => updateProp('ringDuration', val)}
/>
<PreviewSlider
title="Ring Pivot"
min={0}
max={100}
step={2}
value={ringPivot}
valueUnit="%"
onChange={val => updateProp('ringPivot', val)}
/>
<PreviewSlider
title="Crossfade"
min={100}
max={300}
step={10}
value={crossfadeMs}
valueUnit="ms"
onChange={val => updateProp('crossfadeMs', val)}
/>
<PreviewSlider
title="Reveal Bounce"
min={0}
max={0.3}
step={0.05}
value={revealBounce}
onChange={val => updateProp('revealBounce', val)}
/>
<PreviewSwitch title="Badge" isChecked={badge} onChange={val => updateProp('badge', val)} />
<PreviewColorPickerCustom
title="Badge Color"
color={badgeColor}
onChange={val => updateProp('badgeColor', val)}
/>
<PreviewSwitch title="Waves" isChecked={waves} onChange={val => updateProp('waves', val)} />
<PreviewSwitch title="Clapper" isChecked={clapper} onChange={val => updateProp('clapper', val)} />
<PreviewInput
title="Off Label"
value={offLabel}
maxLength={24}
onChange={val => updateProp('offLabel', val)}
/>
<PreviewInput
title="On Label"
value={onLabel}
maxLength={24}
onChange={val => updateProp('onLabel', val)}
/>
<PreviewSwitch title="Disabled" isChecked={disabled} onChange={val => updateProp('disabled', val)} />
</Customize>
<PropTable data={propData} />
<Dependencies dependencyList={['motion', '@hugeicons/react', '@hugeicons/core-free-icons']} />
</PreviewTab>
<CodeTab>
<CodeExample codeObject={bellToggle} componentName="BellToggle" />
</CodeTab>
</TabsLayout>
</ComponentPropsProvider>
);
};
export default BellToggleDemo;