Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Banner Component #90

Merged
merged 16 commits into from
Sep 6, 2023
Merged
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
5 changes: 5 additions & 0 deletions .changeset/sharp-mangos-tease.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@animareflection/ui": minor
---

Add `Banner` component
6 changes: 4 additions & 2 deletions examples/next/src/app/client/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"use client";

import { Flex } from "@animareflection/ui";
import { Flex, Text } from "@animareflection/ui";

import {
AccordionDemo,
BannerDemo,
ButtonDemo,
CheckboxDemo,
CollapseDemo,
Expand All @@ -19,7 +20,7 @@ import {

const ClientPage = () => (
<Flex direction="column" align="center" w="100%" gap={2} p={12}>
Client component demo!
<Text>Client component demo!</Text>
<ButtonDemo />
<AccordionDemo />
<DrawerDemo />
Expand All @@ -32,6 +33,7 @@ const ClientPage = () => (
<SkeletonToggleDemo />
<CheckboxDemo />
<CollapseDemo />
<BannerDemo />
</Flex>
);

Expand Down
11 changes: 11 additions & 0 deletions examples/next/src/components/BannerDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Banner } from "@animareflection/ui/client";

import { Wrapper } from "components";

const BannerDemo = () => (
<Wrapper title="Banner">
<Banner closable>Banner</Banner>
</Wrapper>
);

export default BannerDemo;
1 change: 1 addition & 0 deletions examples/next/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { default as AccordionDemo } from "./AccordionDemo";
export { default as BadgeDemo } from "./BadgeDemo";
export { default as BannerDemo } from "./BannerDemo";
export { default as ButtonDemo } from "./ButtonDemo";
export { default as CardDemo } from "./CardDemo";
export { default as CheckboxDemo } from "./CheckboxDemo";
Expand Down
3 changes: 3 additions & 0 deletions examples/vite-react/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import {
AccordionDemo,
BadgeDemo,
BannerDemo,
ButtonDemo,
CardDemo,
CheckboxDemo,
Expand Down Expand Up @@ -97,6 +98,8 @@ const App = () => (
<CheckboxDemo />
<KbdDemo />
<CollapseDemo />
<BannerDemo />

<Hide below="md">
<Text color="brand.primary.500" fontSize="3xl" fontWeight="bold">
Hide below md breakpoint
Expand Down
12 changes: 12 additions & 0 deletions examples/vite-react/src/components/BannerDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { VStack } from "@animareflection/ui";
import { Banner } from "@animareflection/ui/client";

import { Wrapper } from "components";

const BannerDemo = () => (
<Wrapper title="Banner">
<Banner closable>Banner</Banner>
</Wrapper>
);

export default BannerDemo;
1 change: 1 addition & 0 deletions examples/vite-react/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { default as AccordionDemo } from "./AccordionDemo";
export { default as BadgeDemo } from "./BadgeDemo";
export { default as BannerDemo } from "./BannerDemo";
export { default as ButtonDemo } from "./ButtonDemo";
export { default as CardDemo } from "./CardDemo";
export { default as CheckboxDemo } from "./CheckboxDemo";
Expand Down
30 changes: 30 additions & 0 deletions src/components/client/core/Banner/Banner.recipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { defineRecipe } from "@pandacss/dev";

export const bannerRecipe = defineRecipe({
className: "banner",
description: "The styles for the Banner component",
base: {
textStyle: "md",
fontWeight: "bold",
textAlign: "center",
justifyContent: "center",
display: "inline-flex",
alignItems: "center",
userSelect: "none",
whiteSpace: "nowrap",
w: "full",
position: "relative",
p: 4,
background: "accent.default",
color: "accent.fg",
},
variants: {
variant: {
gradient: {
bgGradient: "to-r",
gradientFrom: "brand.secondary.500",
gradientTo: "accent.default",
},
},
},
});
29 changes: 29 additions & 0 deletions src/components/client/core/Banner/Banner.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { expect } from "@storybook/jest";
import { screen, userEvent, within } from "@storybook/testing-library";

import type { ReactRenderer } from "@storybook/react";
import type { PlayFunctionContext, Renderer } from "@storybook/types";

/**
* Banner testing suite
*/
export const bannerState = async <R extends Renderer = ReactRenderer>({
canvasElement,
step,
}: PlayFunctionContext<R>) => {
const canvas = within(canvasElement as HTMLElement);

const closeButton = await canvas.findByRole("button");

await expect(closeButton).toBeInTheDocument();

await step("It should close banner on close button click", async () => {
const bannerTitle = screen.getByText("Banner");

await expect(bannerTitle).toBeVisible();

await userEvent.click(closeButton);

await expect(bannerTitle).not.toBeVisible();
});
};
59 changes: 59 additions & 0 deletions src/components/client/core/Banner/Banner.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { bannerState } from "./Banner.spec";
import { Banner } from "components/client";
import { VStack } from "generated/panda/jsx";

import type { Meta, StoryObj } from "@storybook/react";

type Story = StoryObj<typeof meta>;

export const Default: Story = {
render: () => (
<VStack position="absolute" inset={0}>
<Banner>Banner</Banner>
</VStack>
),
};

export const Closeable: Story = {
render: () => (
<VStack position="absolute" inset={0}>
<Banner closable>Banner</Banner>
</VStack>
),
};

export const Gradient: Story = {
render: () => (
<VStack position="absolute" inset={0}>
<Banner closable variant="gradient">
Banner
</Banner>
</VStack>
),
};

export const Stacked: Story = {
render: () => (
<VStack gap={0} position="absolute" inset={0}>
<Banner variant="gradient">Banner</Banner>
<Banner>Banner</Banner>
</VStack>
),
};

export const BannerState: Story = {
...Closeable,
play: bannerState,
name: "[TEST] Banner State",
tags: ["test"],
};

// TODO remove explicit type annotation, required due to `pnpm` bug (and therefore Yarn with `pnpm` linker); https://github.com/microsoft/TypeScript/issues/47663
const meta: Meta<typeof Banner> = {
title: "Components/Client/Core/Banner",
component: Banner,
tags: ["autodocs"],
decorators: [(Story) => <Story />],
} satisfies Meta<typeof Banner>;

export default meta;
50 changes: 50 additions & 0 deletions src/components/client/core/Banner/Banner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { useState } from "react";
import { FiX as CloseIcon } from "react-icons/fi";

import Button from "components/client/core/Button/Button";
import Icon from "components/client/core/Icon/Icon";
import { panda } from "generated/panda/jsx";
import { banner } from "generated/panda/recipes";

import type { BannerVariantProps } from "generated/panda/recipes";
import type { ComponentProps } from "react";

export interface Props
extends ComponentProps<typeof panda.div>,
BannerVariantProps {
closable?: boolean;
}

/**
* Core UI banner.
*/
const Banner = ({ children, variant, closable, ...rest }: Props) => {
const [isOpen, setIsOpen] = useState<boolean>(true);

return (
isOpen && (
<panda.div className={banner({ variant })} {...rest}>
{children}
{closable && (
<Button
onClick={() => setIsOpen(false)}
position="absolute"
right={6}
p={1}
_focus={{
outline: "none",
}}
bgColor={{
base: "inherit",
_hover: "none",
}}
>
<Icon as={CloseIcon} color="bg.default" />
</Button>
)}
</panda.div>
)
);
};

export default Banner;
2 changes: 2 additions & 0 deletions src/components/client/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { default as Accordion } from "./Accordion/Accordion";
export { default as Banner } from "./Banner/Banner";
export { default as Button } from "./Button/Button";
export { default as Checkbox } from "./Checkbox/Checkbox";
export { default as Drawer } from "./Drawer/Drawer";
Expand All @@ -13,6 +14,7 @@ export type {
Props as AccordionProps,
AccordionItemRecord,
} from "./Accordion/Accordion";
export type { Props as BannerProps } from "./Banner/Banner";
export type { Props as ButtonProps } from "./Button/Button";
export type { Props as CheckboxProps } from "./Checkbox/Checkbox";
export type { Props as DrawerProps } from "./Drawer/Drawer";
Expand Down
2 changes: 2 additions & 0 deletions src/lib/panda/recipes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { accordionRecipe } from "../../components/client/core/Accordion/Accordion.recipe";
import { bannerRecipe } from "../../components/client/core/Banner/Banner.recipe";
import { buttonRecipe } from "../../components/client/core/Button/Button.recipe";
import { checkboxRecipe } from "../../components/client/core/Checkbox/Checkbox.recipe";
import { drawerRecipe } from "../../components/client/core/Drawer/Drawer.recipe";
Expand All @@ -18,6 +19,7 @@ import { showRecipe } from "../../components/universal/utility/Show/Show.recipe"

export const recipes = {
badge: badgeRecipe,
banner: bannerRecipe,
button: buttonRecipe,
hide: hideRecipe,
image: imageRecipe,
Expand Down