diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx index 0892d7e..94d2169 100644 --- a/src/components/Button/Button.tsx +++ b/src/components/Button/Button.tsx @@ -40,7 +40,7 @@ export const Button: React.FC = ({ return ( = { - title: "Components (WIP)/Card", + title: "Components/Card", component: Card, tags: ["autodocs"], argTypes: {}, args: { - title: "Card Example", - description: - "Ever wondered why keyboards aren't arranged in alphabetical order? Probably because someone in the 1870s had a really good time watching people hunt and peck for letters. QWERTY layout: the original practical joke that became a global standard.", - image: (props) => ( - Card Image + Button + + } + variant="default" /> ), + image: ( + + Tag 1 + Tag 2 + + } + > + Card Image + + ), imageLocation: "left", - mainLabels: Main Label, - secondaryLabels: Secondary Label, - variant: "light", }, }; @@ -34,20 +51,218 @@ export const Basic: Story = { args: {}, }; -export const Dark: Story = { - args: { - variant: "dark", - }, -}; - export const ImageOnTheRight: Story = { args: { + children: ( + + ), imageLocation: "right", }, }; export const ImageOnTheTop: Story = { args: { + children: ( + + ), imageLocation: "top", }, }; + +export const ImageWithMainAndSecondaryLabels: Story = { + args: { + children: ( + + ), + image: ( + + Tag 1 + Tag 2 + + } + secondaryLabels={ + <> + Tag 3 + Tag 4 + + } + > + Card Image + + ), + }, +}; + +/** For a Tagline card, use `CardContent.Tagline` and no image. */ +export const WithTagline: Story = { + args: { + image: undefined, + imageLocation: undefined, + children: ( + + + + + } + /> + ), + }, +}; + +/** Set the "secondary" variant and "clickable" to get a hover, use `asChild` to have an `a` tag as the root element, then use `CardContent.Link` to render the content. */ +export const Link: Story = { + args: { + variant: "secondary", + clickable: true, + asChild: true, + image: undefined, + children: ( + + } + title="Join the Ink Revolution!" + description="Did you know that Ink's design system is like a chameleon for your UI? Just like these color-changing lizards adapt to their environment, Ink components seamlessly blend into any design while maintaining their unique personality. Just fabulous, adaptable UI!" + /> + + ), + }, +}; + +export const LargeLinks: Story = { + args: { + image: ( + Main Label}> + Card Image + + ), + children: ( + <> + + + + + Design Tips & Tricks + + + + + Color Theory 101 + + + + + Typography Essentials + + + + + Accessibility Best Practices + + + + + Animation Fundamentals + + + + + Responsive Design Guide + + + + + ), + }, +}; + +export const LargeCardInfo: Story = { + args: { + image: ( + Main Label}> + Card Image + + ), + children: ( + <> + + + + } + title="Pizza Making Class" + description="Learn to toss dough and create your perfect pizza with our expert chefs." + /> + + + } + title="Paint & Sip Night" + description="Enjoy wine while creating your masterpiece in this relaxing art class." + /> + + + } + title="Live Jazz Night" + description="Swing by for smooth tunes and great vibes at our local jazz club." + /> + + + } + title="Community Garden" + description="Get your hands dirty and learn about urban farming with neighbors." + /> + + + } + title="Weekend Food Festival" + description="Sample delicious treats from local vendors and enjoy live entertainment all weekend long." + /> + + + + ), + }, +}; diff --git a/src/components/Card/Card.tsx b/src/components/Card/Card.tsx index fac0247..4574719 100644 --- a/src/components/Card/Card.tsx +++ b/src/components/Card/Card.tsx @@ -1,102 +1,92 @@ import React from "react"; import { classNames } from "../../util/classes"; -import { Slot } from "../Slot"; +import { Slot, Slottable } from "../Slot"; +import { cva, type VariantProps } from "class-variance-authority"; -export interface CardProps { - title: React.ReactNode; - description: React.ReactNode; +export interface CardProps extends VariantProps { className?: string; - image?: (props: { className: string }) => React.ReactNode; - imageLocation?: "left" | "right" | "top"; - mainLabels?: React.ReactNode; - secondaryLabels?: React.ReactNode; - variant?: "light" | "dark"; + children: React.ReactNode; + image?: React.ReactNode; + clickable?: boolean; asChild?: boolean; } +const cardVariants = cva( + ` +ink:rounded-md lg:ink:rounded-lg +ink:grid ink:grid-cols-1 +ink:p-2 ink:pb-3 ink:sm:p-3 ink:gap-3 +ink:relative +ink:bg-background-container +ink:font-default +ink:box-border +`, + { + variants: { + variant: { + default: "ink:bg-background-container", + "light-purple": "ink:bg-ink-light-purple", + secondary: "ink:bg-button-secondary", + }, + imageLocation: { + left: "ink:sm:grid-cols-2", + right: "ink:sm:grid-cols-2", + top: "ink:sm:grid-cols-1", + }, + clickable: { + true: "ink:cursor-pointer", + false: "", + }, + }, + compoundVariants: [ + { + variant: "secondary", + clickable: true, + className: "ink:hover:bg-button-secondary-hover", + }, + ], + } +); + export const Card: React.FC = ({ - title, - description, + children, className, image, imageLocation, - mainLabels, - secondaryLabels, - variant, asChild, + variant, + clickable, }) => { const Component = asChild ? Slot : "div"; return ( -
-
- {(mainLabels || secondaryLabels) && ( -
-
{mainLabels}
-
- {secondaryLabels} -
-
- )} - {image && ( + + {(child) => ( + <> + {image}
- {image({ - className: classNames( - "ink:object-cover ink:object-center ink:w-full ink:h-full" - ), - })} + {child}
- )} -
-
-
)} - > -

{title}

-
- {description} -
-
+
); }; diff --git a/src/components/Card/Content/CallToAction.tsx b/src/components/Card/Content/CallToAction.tsx new file mode 100644 index 0000000..a41e730 --- /dev/null +++ b/src/components/Card/Content/CallToAction.tsx @@ -0,0 +1,33 @@ +import { classNames } from "../../../util/classes"; +import { TitleAndDescription } from "./TitleAndDescription"; + +interface CallToActionProps { + title: React.ReactNode; + description: React.ReactNode; + button: React.ReactNode; + variant: "default" | "light"; + className?: string; +} + +export const CallToAction: React.FC = ({ + title, + description, + button, + variant, + className, +}) => { + return ( +
+ +
{button}
+
+ ); +}; diff --git a/src/components/Card/Content/CardInfo.tsx b/src/components/Card/Content/CardInfo.tsx new file mode 100644 index 0000000..277a1c3 --- /dev/null +++ b/src/components/Card/Content/CardInfo.tsx @@ -0,0 +1,20 @@ +import { PropsWithChildren } from "react"; +import { classNames } from "../../../util/classes"; + +export interface CardInfoProps extends PropsWithChildren { + className?: string; + children: React.ReactNode; +} + +export const CardInfo = ({ children, className }: CardInfoProps) => { + return ( +
+ {children} +
+ ); +}; diff --git a/src/components/Card/Content/Image.tsx b/src/components/Card/Content/Image.tsx new file mode 100644 index 0000000..5cefd56 --- /dev/null +++ b/src/components/Card/Content/Image.tsx @@ -0,0 +1,48 @@ +import * as React from "react"; +import { classNames } from "../../../util/classes"; +import { Slot } from "../../Slot"; + +export interface ImageProps extends React.PropsWithChildren { + className?: string; + mainLabels?: React.ReactNode; + secondaryLabels?: React.ReactNode; +} + +export const Image: React.FC = ({ + className, + mainLabels, + secondaryLabels, + children, +}) => { + return ( +
+ {(mainLabels || secondaryLabels) && ( +
+
+ {mainLabels} +
+ {secondaryLabels && ( +
+ {secondaryLabels} +
+ )} +
+ )} + + {children} + +
+ ); +}; diff --git a/src/components/Card/Content/LargeLink.tsx b/src/components/Card/Content/LargeLink.tsx new file mode 100644 index 0000000..e72b546 --- /dev/null +++ b/src/components/Card/Content/LargeLink.tsx @@ -0,0 +1,49 @@ +import { PropsWithChildren } from "react"; +import { classNames } from "../../../util/classes"; +import { Slot, Slottable } from "../../Slot"; +import { InkIcon } from "../../.."; + +export interface LargeLinkProps extends PropsWithChildren { + className?: string; + asChild?: boolean; + linkIcon?: React.ReactNode; + href?: string; + target?: string; +} + +export const LargeLink = ({ + children, + className, + asChild, + linkIcon = ( + + ), + href, + target, +}: LargeLinkProps) => { + const Component = asChild ? Slot : "a"; + return ( + + + {(child) => ( + <> +
+ {child} +
+ {linkIcon} + + )} +
+
+ ); +}; diff --git a/src/components/Card/Content/LargeLinks.tsx b/src/components/Card/Content/LargeLinks.tsx new file mode 100644 index 0000000..583c425 --- /dev/null +++ b/src/components/Card/Content/LargeLinks.tsx @@ -0,0 +1,20 @@ +import { PropsWithChildren } from "react"; +import { classNames } from "../../../util/classes"; + +export interface LargeLinksProps extends PropsWithChildren { + className?: string; + children: React.ReactNode; +} + +export const LargeLinks = ({ children, className }: LargeLinksProps) => { + return ( +
+ {children} +
+ ); +}; diff --git a/src/components/Card/Content/Link.tsx b/src/components/Card/Content/Link.tsx new file mode 100644 index 0000000..4cf7242 --- /dev/null +++ b/src/components/Card/Content/Link.tsx @@ -0,0 +1,31 @@ +import { InkIcon } from "../../.."; +import { Tiny } from "./Tiny"; + +interface LinkProps { + className?: string; + title: string; + description: string; + icon?: React.ReactNode; + linkIcon?: React.ReactNode; +} + +export const Link = ({ + className, + title, + description, + icon, + linkIcon = , +}: LinkProps) => { + return ( + {icon} : undefined} + title={title} + description={description} + > + {linkIcon && ( +
{linkIcon}
+ )} +
+ ); +}; diff --git a/src/components/Card/Content/Tagline.tsx b/src/components/Card/Content/Tagline.tsx new file mode 100644 index 0000000..14259f6 --- /dev/null +++ b/src/components/Card/Content/Tagline.tsx @@ -0,0 +1,30 @@ +import { classNames } from "../../../util/classes"; + +interface TaglineProps { + title: React.ReactNode; + buttons: React.ReactNode; + className?: string; +} + +export const Tagline: React.FC = ({ + title, + buttons, + className, +}) => { + return ( +
+

+ {title} +

+ +
+ {buttons} +
+
+ ); +}; diff --git a/src/components/Card/Content/Tiny.tsx b/src/components/Card/Content/Tiny.tsx new file mode 100644 index 0000000..311d055 --- /dev/null +++ b/src/components/Card/Content/Tiny.tsx @@ -0,0 +1,34 @@ +import { PropsWithChildren } from "react"; +import { classNames } from "../../../util/classes"; +import { TitleAndDescription } from "./TitleAndDescription"; + +export interface TinyProps extends PropsWithChildren { + className?: string; + icon?: React.ReactNode; + title: string; + description: string; +} +export const Tiny = ({ + className, + icon, + title, + description, + children, +}: TinyProps) => { + return ( +
+ {icon} + + {children} +
+ ); +}; diff --git a/src/components/Card/Content/TitleAndDescription.tsx b/src/components/Card/Content/TitleAndDescription.tsx new file mode 100644 index 0000000..956f136 --- /dev/null +++ b/src/components/Card/Content/TitleAndDescription.tsx @@ -0,0 +1,42 @@ +import { classNames, variantClassNames } from "../../../util/classes"; + +export interface TitleAndDescriptionProps { + title: React.ReactNode; + description?: React.ReactNode; + size?: "default" | "small"; +} + +export const TitleAndDescription = ({ + title, + description, + size = "default", +}: TitleAndDescriptionProps) => { + return ( +
+

+ {title} +

+ {description && ( +

+ {description} +

+ )} +
+ ); +}; diff --git a/src/components/Card/Content/index.ts b/src/components/Card/Content/index.ts new file mode 100644 index 0000000..9fb6dfd --- /dev/null +++ b/src/components/Card/Content/index.ts @@ -0,0 +1,9 @@ +export * from "./CallToAction"; +export * from "./CardInfo"; +export * from "./Image"; +export * from "./LargeLink"; +export * from "./LargeLinks"; +export * from "./Link"; +export * from "./Tagline"; +export * from "./Tiny"; +export * from "./TitleAndDescription"; diff --git a/src/components/Card/index.ts b/src/components/Card/index.ts index 24d3212..72a9b89 100644 --- a/src/components/Card/index.ts +++ b/src/components/Card/index.ts @@ -1 +1,2 @@ +export * as CardContent from "./Content"; export * from "./Card"; diff --git a/src/components/Pill/Pill.tsx b/src/components/Pill/Pill.tsx deleted file mode 100644 index 36e8b02..0000000 --- a/src/components/Pill/Pill.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { PropsWithChildren } from "react"; - -export interface PillProps { - icon?: React.ReactNode; -} - -export const Pill: React.FC = ({ - icon, - children, -}) => { - return ( -
- - {icon} - {children} - -
- ); -}; diff --git a/src/components/Pill/index.ts b/src/components/Pill/index.ts deleted file mode 100644 index ca842ed..0000000 --- a/src/components/Pill/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./Pill"; diff --git a/src/components/SegmentedControl/SegmentedControl.tsx b/src/components/SegmentedControl/SegmentedControl.tsx index 17e9a42..50901e3 100644 --- a/src/components/SegmentedControl/SegmentedControl.tsx +++ b/src/components/SegmentedControl/SegmentedControl.tsx @@ -63,7 +63,7 @@ export const SegmentedControl = ({
= ({ } className={classNames( "ink:fixed ink:lg:hidden ink:lg:pointer-events-none ink:inset-0 ink:top-[var(--ink-mobile-nav-height)] ink:z-50", - "ink:bg-background-light/20 ink:backdrop-blur-xl", + "ink:bg-background-light/20 ink:backdrop-blur-lg", "ink:transition-default-animation ink:opacity-100 ink:starting:opacity-0" )} > diff --git a/src/styles/Colors.stories.tsx b/src/styles/Colors.stories.tsx index fb26a3f..da3b447 100644 --- a/src/styles/Colors.stories.tsx +++ b/src/styles/Colors.stories.tsx @@ -15,8 +15,13 @@ function Colors() { "ink:bg-status-error-bg ink:text-status-error", "ink:bg-status-alert-bg ink:text-status-alert", ]; + const independentColors = [ + "ink:bg-ink-light-purple ink:text-text-on-primary", + "ink:bg-ink-dark-purple ink:text-text-on-primary", + ]; return (
+

Colors

{colors.map((color) => (
))} +

+ Theme-Independent Colors +

+ {independentColors.map((color) => ( +
+ {color} +
+ ))}
); } diff --git a/src/styles/theme/colors.base.css b/src/styles/theme/colors.base.css index 49701c2..b134636 100644 --- a/src/styles/theme/colors.base.css +++ b/src/styles/theme/colors.base.css @@ -112,4 +112,12 @@ var(--ink-background-shadow) 10%, transparent ); + + /* Blur */ + --ink-base-blur-sm: 48px; + --ink-base-blur-lg: 128px; + + /* Independent Colors */ + --ink-color-light-purple: #b9aaef; + --ink-color-dark-purple: #5c479d; } diff --git a/src/tailwind.css b/src/tailwind.css index 6a3b66e..228f955 100644 --- a/src/tailwind.css +++ b/src/tailwind.css @@ -111,6 +111,10 @@ --color-default-app-icon-gradient: var(--ink-default-app-icon-gradient); + /* Independent Colors */ + --color-ink-light-purple: var(--ink-color-light-purple); + --color-ink-dark-purple: var(--ink-color-dark-purple); + /* Typography */ --text-*: initial; @@ -142,9 +146,13 @@ --text-h5--line-height: 24px; --text-h5--font-weight: 700; - --text-body-1: 18px; - --text-body-1--line-height: 24px; - --text-body-1--font-weight: 400; + --text-body-1-regular: 18px; + --text-body-1-regular--line-height: 24px; + --text-body-1-regular--font-weight: 400; + + --text-body-1-bold: 18px; + --text-body-1-bold--line-height: 24px; + --text-body-1-bold--font-weight: 600; --text-body-2-regular: 16px; --text-body-2-regular--line-height: 20px; @@ -198,6 +206,15 @@ --shadow-xs: 0px 4px 8px -2px var(--ink-base-shadow-xs-color); --shadow-md: 0px 12px 16px -4px var(--ink-base-shadow-md-color); --shadow-lg: 0px 32px 64px -12px var(--ink-base-shadow-lg-color); + + /* Blur */ + --blur-*: initial; + --blur-sm: var(--ink-base-blur-sm); + --blur-lg: var(--ink-base-blur-lg); + + /* Rotation */ + --rotation-225: 225deg; + --rotation-270: 270deg; } @utility transition-default-animation { diff --git a/src/util/classes.ts b/src/util/classes.ts index 3de393f..5dccaec 100644 --- a/src/util/classes.ts +++ b/src/util/classes.ts @@ -43,7 +43,8 @@ const customTwMerge = extendTailwindMerge({ "text-h2", "text-h3", "text-h4", - "text-body-1", + "text-body-1-regular", + "text-body-1-bold", "text-body-2-regular", "text-body-2-bold", "text-body-3-regular",