Skip to content

Commit

Permalink
Floating checklist (#424)
Browse files Browse the repository at this point in the history
Added Checklist.Floating, Progress.Ring, Popover, useFloating, useAutoScroll.
  • Loading branch information
dfltr authored Feb 6, 2025
1 parent fc19d79 commit 3c7396d
Show file tree
Hide file tree
Showing 21 changed files with 793 additions and 64 deletions.
21 changes: 21 additions & 0 deletions apps/smithy/src/stories/Checklist/Floating.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Box, Checklist } from "@frigade/react";
import { type Meta } from "@storybook/react";

export default {
title: "Components/Checklist/Floating",
component: Checklist.Floating,
decorators: [
(Story) => (
<Box backgroundColor="gray900">
<Story />
</Box>
),
],
} as Meta<typeof Checklist.Floating>;

export const Default = {
args: {
//children: <Button.Primary title="Open!" />,
flowId: "flow_K2dmIlteW8eGbGoo",
},
};
75 changes: 75 additions & 0 deletions apps/smithy/src/stories/Popover/Popover.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Meta, StoryObj } from "@storybook/react";
import { Box, Button, Card, FloatingUI, Popover } from "@frigade/react";

const meta: Meta<typeof Popover> = {
title: "Design System/Popover",
parameters: {
layout: "centered",
},
};
export default meta;

type Story = StoryObj<typeof Popover>;

export const Default: Story = {
render: () => (
<Box>
<Popover.Root>
<Popover.Trigger>
<Button.Primary>Popover.Trigger</Button.Primary>
</Popover.Trigger>
<Popover.Content>
<Card borderWidth="md">
<Card.Title>Popover.Content</Card.Title>
</Card>
</Popover.Content>
</Popover.Root>
</Box>
),
};

export const Nested: Story = {
render: () => (
<FloatingUI.FloatingTree>
<Box>
<Popover.Root>
<Popover.Trigger>
<Button.Primary>Popover.Trigger</Button.Primary>
</Popover.Trigger>
<Popover.Content>
<Card borderWidth="md">
<Card.Title>Popover.Content</Card.Title>
<Popover.Root>
<Popover.Trigger>
<Button.Primary>Nested Popover.Trigger</Button.Primary>
</Popover.Trigger>
<Popover.Content>
<Card borderWidth="md">
<Card.Title>Nested Popover.Content</Card.Title>
</Card>
</Popover.Content>
</Popover.Root>
</Card>
</Popover.Content>
</Popover.Root>
</Box>
</FloatingUI.FloatingTree>
),
};

export const AutoScroll: Story = {
render: () => (
<Box marginTop="200vh">
<Popover.Root autoScroll open>
<Popover.Trigger>
<Button.Primary>Popover.Trigger</Button.Primary>
</Popover.Trigger>
<Popover.Content>
<Card borderWidth="md">
<Card.Title>Popover.Content</Card.Title>
</Card>
</Popover.Content>
</Popover.Root>
</Box>
),
};
29 changes: 25 additions & 4 deletions apps/smithy/src/stories/Progress/Progress.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Flex, Progress } from "@frigade/react";
import { Flex, Progress, Text } from "@frigade/react";

export default {
title: "Design System/Progress",
Expand All @@ -13,9 +13,30 @@ export const Default = {
decorators: [
(_, { args }) => (
<Flex.Column gap={5}>
<Progress.Bar {...args} />
<Progress.Dots {...args} />
<Progress.Segments {...args} />
<Flex.Column gap={1}>
<Text.H3>Progress.Bar</Text.H3>
<Progress.Bar maxWidth="200px" {...args} />
</Flex.Column>
<Flex.Column gap={1}>
<Text.H3>Progress.Dots</Text.H3>
<Progress.Dots {...args} />
</Flex.Column>
<Flex.Column gap={1}>
<Text.H3>Progress.Segments</Text.H3>
<Progress.Segments maxWidth="200px" {...args} />
</Flex.Column>
<Flex.Column gap={1}>
<Text.H3>Progress.Ring</Text.H3>
<Flex.Row gap="2">
<Progress.Ring {...args} showLabel />
<Progress.Ring
{...args}
height="24px"
strokeWidth="4px"
width="24px"
/>
</Flex.Row>
</Flex.Column>
</Flex.Column>
),
],
Expand Down
1 change: 1 addition & 0 deletions apps/smithy/src/stories/Tour/Tour.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default {
export const Default = {
args: {
align: "after",
autoScroll: false,
dismissible: true,
defaultOpen: true,
flowId: "flow_U63A5pndRrvCwxNs",
Expand Down
26 changes: 26 additions & 0 deletions apps/smithy/src/stories/hooks/useAutoScroll.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useState } from "react";
import { Box, Button, Flex, useAutoScroll } from "@frigade/react";

export default {
title: "Hooks/useAutoScroll",
};

export const Default = {
args: {
scrollOptions: true,
},

decorators: [
() => {
const [scrollRef, setScrollRef] = useState<Element>();

useAutoScroll(scrollRef);

return (
<Box backgroundColor="pink" marginTop="200vh" ref={setScrollRef}>
Scroll to this element
</Box>
);
},
],
};
7 changes: 4 additions & 3 deletions packages/react/src/components/CheckIndicator/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ function CheckIcon() {

interface CheckIndicatorProps extends BoxProps {
checked?: boolean
size?: string
}

export function CheckIndicator({ checked = false, ...props }: CheckIndicatorProps) {
export function CheckIndicator({ checked = false, size = '22px', ...props }: CheckIndicatorProps) {
return (
<Box
backgroundColor="inherit"
Expand All @@ -37,8 +38,8 @@ export function CheckIndicator({ checked = false, ...props }: CheckIndicatorProp
padding="0"
part="check-indicator"
position="relative"
height="22px"
width="22px"
height={size}
width={size}
{...props}
>
{checked && (
Expand Down
33 changes: 33 additions & 0 deletions packages/react/src/components/Checklist/Floating.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
export const floatingTransitionCSS = {
'&[data-status="open"]': {
opacity: 1,
zIndex: 1,
},
'&[data-status="close"]': {
opacity: 0,
zIndex: 0,

'& [data-status="close"]': {
display: 'none',
},
},
'&[data-status="initial"]': {
opacity: 0.8,
},
'&[data-status="open"], &[data-status="close"]': {
transition: 'transform 0.2s ease-out, opacity 0.2s ease-out',
},
'&[data-status="initial"] .fr-popover-transition-container': {
transform: 'scale(0.8)',
},
'&[data-status="close"] .fr-popover-transition-container': {
transform: 'scale(0.3)',
},
'&[data-status="open"] .fr-popover-transition-container': {
transform: 'scale(1)',
},
'& .fr-popover-transition-container': {
transformOrigin: 'left',
transition: 'transform 0.2s ease-out',
},
}
141 changes: 141 additions & 0 deletions packages/react/src/components/Checklist/Floating.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { useRef, useState } from 'react'
import { FloatingTree } from '@floating-ui/react'

import { Card } from '@/components/Card'
import { Flex } from '@/components/Flex'
import { Flow, type FlowPropsWithoutChildren } from '@/components/Flow'
import * as Popover from '@/components/Popover'
import * as Progress from '@/components/Progress'
import { Text } from '@/components/Text'

import { FloatingStep } from '@/components/Checklist/FloatingStep'
import { floatingTransitionCSS } from '@/components/Checklist/Floating.styles'

export interface FloatingChecklistProps
extends Popover.PopoverRootProps,
FlowPropsWithoutChildren {}

// TODO: Fix props here (split popover and flow props and pass them to Flow / Popover.Root)
export function Floating({
children,
flowId,
onPrimary,
onSecondary,
part,
...props
}: FloatingChecklistProps) {
const [openStepId, setOpenStepId] = useState(null)
const pointerLeaveTimeout = useRef<ReturnType<typeof setTimeout>>()

function handlePointerEnter() {
clearTimeout(pointerLeaveTimeout.current)
}

function handlePointerLeave() {
clearTimeout(pointerLeaveTimeout.current)

if (openStepId != null) {
pointerLeaveTimeout.current = setTimeout(() => setOpenStepId(null), 300)
}
}

function resetOpenStep(isOpen: boolean) {
if (!isOpen && openStepId != null) {
setOpenStepId(null)
}
}

return (
<Flow flowId={flowId} part={['floating-checklist', part]} {...props}>
{({ flow }) => {
const currentSteps = flow.getNumberOfCompletedSteps()
const availableSteps = flow.getNumberOfAvailableSteps()

const anchorContent = children ?? (
<Flex.Row
alignItems="center"
backgroundColor="neutral.background"
border="md solid neutral.border"
borderRadius="md"
cursor="pointer"
gap="2"
padding="1 2"
part="floating-checklist-anchor"
userSelect="none"
>
<Text.Body2 fontWeight="medium" part="floating-checklist-title">
{flow.title}
</Text.Body2>
<Progress.Ring
current={currentSteps}
height="24px"
strokeWidth="4px"
total={availableSteps}
width="24px"
/>
</Flex.Row>
)

return (
<FloatingTree>
<Popover.Root align="start" onOpenChange={resetOpenStep} sideOffset={4}>
<Popover.Trigger display="inline-block">{anchorContent}</Popover.Trigger>

<Popover.Content
css={{
...floatingTransitionCSS,
'&[data-status="initial"]': {
opacity: 0.3,
},
'& .fr-popover-transition-container': {
transformOrigin: 'top left',
transition: 'transform 0.2s ease-out',
},
'&[data-placement^="top"] .fr-popover-transition-container': {
transformOrigin: 'bottom left',
},
}}
>
<Card
backgroundColor="neutral.background"
border="md solid neutral.border"
borderRadius="md"
gap="0"
onPointerEnter={handlePointerEnter}
onPointerLeave={handlePointerLeave}
p="0 1 1"
part="floating-checklist-step-list"
>
<Progress.Bar
borderRadius="md md 0 0"
clipPath="border-box"
css={{
'& .fr-progress-bar-fill': {
borderRadius: 0,
},
}}
current={currentSteps}
height="5px"
total={availableSteps}
flexGrow={1}
margin="0 -1 2"
/>
{Array.from(flow.steps.values()).map((step) => (
<FloatingStep
key={step.id}
onPrimary={onPrimary}
onSecondary={onSecondary}
openStepId={openStepId}
setOpenStepId={setOpenStepId}
step={step}
/>
))}
</Card>
</Popover.Content>
</Popover.Root>
</FloatingTree>
)
}}
</Flow>
)
}
Loading

0 comments on commit 3c7396d

Please sign in to comment.