Skip to content

Commit

Permalink
Add FeatureCard component
Browse files Browse the repository at this point in the history
  • Loading branch information
piotrkulpinski committed Jan 12, 2024
1 parent bf51e66 commit 675b545
Show file tree
Hide file tree
Showing 16 changed files with 251 additions and 28 deletions.
1 change: 0 additions & 1 deletion .storybook/preview-head.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,5 @@

:root {
--font-sans: "Inter", sans-serif;
text-align: center;
}
</style>
2 changes: 1 addition & 1 deletion src/typography/Heading/Heading.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export default {
title: "Typography/Heading",
component: Heading,
args: {
children: "The five boxing wizards jump quickly.",
...Heading.defaultProps,
children: "The five boxing wizards jump quickly.",
},
} satisfies Meta

Expand Down
2 changes: 1 addition & 1 deletion src/typography/Paragraph/Paragraph.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ export default {
title: "Typography/Paragraph",
component: Paragraph,
args: {
...Paragraph.defaultProps,
children:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
...Paragraph.defaultProps,
},
} satisfies Meta

Expand Down
2 changes: 1 addition & 1 deletion src/typography/Prose/Prose.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export default {
title: "Typography/Prose",
component: Prose,
args: {
...Prose.defaultProps,
children: (
<>
<h1>Heading 1</h1>
Expand Down Expand Up @@ -42,7 +43,6 @@ export default {
</ul>
</>
),
...Prose.defaultProps,
},
} satisfies Meta

Expand Down
2 changes: 1 addition & 1 deletion src/typography/Subheading/Subheading.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export default {
title: "Typography/Subheading",
component: Subheading,
args: {
children: "The quick brown fox jumps over the lazy dog.",
...Subheading.defaultProps,
children: "The quick brown fox jumps over the lazy dog.",
},
} satisfies Meta

Expand Down
15 changes: 7 additions & 8 deletions src/ui/AvatarGroup/AvatarGroup.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,40 +24,39 @@ const items = [
export default {
title: "UI/AvatarGroup",
component: AvatarGroup,
args: {
...AvatarGroup.defaultProps,
items,
},
} satisfies Meta

// Stories
export const Default = {
args: {
items,
},
args: {},
} satisfies Story

export const WithLabel = {
args: {
items,
label: "+3",
},
} satisfies Story

export const WithCustomLabel = {
args: {
items,
label: <IconUserPlus />,
},
} satisfies Story

export const WithPreviousOnTop = {
args: {
items,
label: "+3",
previousOnTop: true,
},
} satisfies Story

export const WithCustomMarkup = {
render: () => (
<AvatarGroup.Root size="lg">
render: (props) => (
<AvatarGroup.Root {...props} size="lg">
{items.map((item, i) => (
<AvatarGroup.Item key={i} size="lg" {...item} />
))}
Expand Down
2 changes: 1 addition & 1 deletion src/ui/AvatarGroup/AvatarGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ const AvatarGroupItem = forwardRef<AvatarElement, ComponentPropsWithoutRef<typeo
},
)

const AvatarGroupBase = forwardRef<HTMLDivElement, AvatarGroupProps>((props, ref) => {
const AvatarGroupBase = forwardRef<AvatarGroupElement, AvatarGroupProps>((props, ref) => {
const { items, children, theme, size, shape, previousOnTop, label, ...rest } = props
const avatarProps = { theme, size, shape }

Expand Down
11 changes: 5 additions & 6 deletions src/ui/Badge/Badge.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ type Story = StoryObj<typeof Badge>
export default {
title: "UI/Badge",
component: Badge,
args: {
...Badge.defaultProps,
children: "Badge",
},
} satisfies Meta

// Stories
export const Default = {
args: {
children: "Badge",
...Badge.defaultProps,
},
args: {},
} satisfies Story

export const AsChild = {
Expand All @@ -28,14 +29,12 @@ export const AsChild = {

export const WithPrefix = {
args: {
children: "Badge",
prefix: <IconBolt />,
},
} satisfies Story

export const WithSuffix = {
args: {
children: "Badge",
suffix: <IconBolt />,
},
} satisfies Story
4 changes: 1 addition & 3 deletions src/ui/Button/Button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ export default {
// Stories
export const Default = {
args: {
...Button.defaultProps,
children: "Button",
disabled: false,
...Button.defaultProps,
},
} satisfies Story

Expand All @@ -29,14 +29,12 @@ export const AsChild = {

export const WithPrefix = {
args: {
children: "Button",
prefix: <IconBolt />,
},
} satisfies Story

export const WithSuffix = {
args: {
children: "Button",
suffix: <IconBolt />,
},
} satisfies Story
7 changes: 4 additions & 3 deletions src/ui/Dot/Dot.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ type Story = StoryObj<typeof Dot>
export default {
title: "UI/Dot",
component: Dot,
args: {
...Dot.defaultProps,
},
} satisfies Meta

// Stories
export const Default = {
args: {
...Dot.defaultProps,
},
args: {},
} satisfies Story

export const AsChild = {
Expand Down
2 changes: 1 addition & 1 deletion src/ui/Dot/Dot.variants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { cva } from "~/shared/cva"

export const dotVariants = cva({
base: "block rounded-full border border-transparent text-white",
base: "block rounded-full border border-transparent",

variants: {
theme: {
Expand Down
62 changes: 62 additions & 0 deletions src/ui/FeatureCard/FeatureCard.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { Meta, StoryObj } from "@storybook/react"
import { IconHeadset, IconSquareX } from "@tabler/icons-react"

import { Paragraph } from "~/typography/Paragraph"

import { FeatureCard } from "./FeatureCard"

type Story = StoryObj<typeof FeatureCard>

const Card = (
<div className="flex flex-wrap items-center gap-x-2.5 gap-y-3">
<IconHeadset />
<Paragraph className="font-medium">Need support?</Paragraph>

<Paragraph size="sm" className="w-full opacity-60">
Contact with one of our experts to get support.
</Paragraph>
</div>
)

// Meta
export default {
title: "UI/FeatureCard",
component: FeatureCard,
args: {
...FeatureCard.defaultProps,
children: Card,
style: { width: 240 },
closer: true,
},
} satisfies Meta

// Stories
export const Default = {
args: {},
} satisfies Story

export const AsChild = {
args: {
asChild: true,
closer: false,
children: <a href="/">{Card}</a>,
},
} satisfies Story

export const WithCustomMarkup = {
args: {
closer: false,
},

render: (props) => (
<FeatureCard.Root {...props}>
{Card}

<FeatureCard.Closer asChild className="text-base">
<button>
<IconSquareX />
</button>
</FeatureCard.Closer>
</FeatureCard.Root>
),
} satisfies Story
92 changes: 92 additions & 0 deletions src/ui/FeatureCard/FeatureCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { Slot } from "@radix-ui/react-slot"
import { IconX } from "@tabler/icons-react"
import { forwardRef, isValidElement } from "react"
import type { ButtonHTMLAttributes, HTMLAttributes } from "react"

import { useTheme } from "~/providers"
import { type VariantProps } from "~/shared/cva"
import { Slottable } from "~/utils/Slottable"

import { featureCardCloserVariants, featureCardVariants } from "./FeatureCard.variants"

export type FeatureCardElement = HTMLDivElement

export type FeatureCardProps = HTMLAttributes<FeatureCardElement> &
VariantProps<typeof featureCardVariants> & {
/**
* If set to `true`, the button will be rendered as a child within the component.
* This child component must be a valid React component.
*/
asChild?: boolean

/**
* If set to `true`, it'll render a closer button.
*/
closer?: boolean
}

type FeatureCardCloserProps = ButtonHTMLAttributes<HTMLButtonElement> &
VariantProps<typeof featureCardCloserVariants> & {
/**
* If set to `true`, the button will be rendered as a child within the component.
* This child component must be a valid React component.
*/
asChild?: boolean
}

const FeatureCardRoot = forwardRef<FeatureCardElement, FeatureCardProps>((props, ref) => {
const { className, asChild, theme: propTheme, variant, ...rest } = props

const globalTheme = useTheme()
const theme = propTheme || globalTheme

const useAsChild = asChild && isValidElement(rest.children)
const Component = useAsChild ? Slot : "div"

return (
<Component className={featureCardVariants({ theme, variant, className })} ref={ref} {...rest} />
)
})

const FeatureCardCloser = forwardRef<HTMLButtonElement, FeatureCardCloserProps>((props, ref) => {
const { children, className, asChild, ...rest } = props

const useAsChild = asChild && isValidElement(children)
const Component = useAsChild ? Slot : "button"

return (
<Component ref={ref} className={featureCardCloserVariants({ className })} {...rest}>
{useAsChild ? children : <IconX />}
</Component>
)
})

export const FeatureCardBase = forwardRef<FeatureCardElement, FeatureCardProps>((props, ref) => {
const { children, asChild, closer, ...rest } = props

return (
<FeatureCardRoot ref={ref} asChild={asChild} {...rest}>
<Slottable child={children} asChild={asChild}>
{(child) => (
<>
{child}
{closer && <FeatureCardCloser />}
</>
)}
</Slottable>
</FeatureCardRoot>
)
})

export const FeatureCard = Object.assign(FeatureCardBase, {
Root: FeatureCardRoot,
Closer: FeatureCardCloser,
})

FeatureCard.defaultProps = {
variant: "soft",
asChild: false,
closer: false,
}

FeatureCard.displayName = "FeatureCard"
Loading

0 comments on commit 675b545

Please sign in to comment.