Fix CardInset padding decoupling and logo a11y
Address PR review feedback identified by cubic: - Re-key CardInset off the Card's resolved padding (shared via context) instead of size, so insets line up even when padding is decoupled from size via the padding prop; padding="none" becomes a no-op. - Give ProboLogo a default aria-label so its role="img" SVG has an accessible name when used standalone; callers can still override it. - Make the ProboLogo story render spread args so Storybook controls work. Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
@@ -28,7 +28,7 @@ export function Card(props: CardProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={card({ size, padding, variant, interactive, className })} {...rest}>
|
<div className={card({ size, padding, variant, interactive, className })} {...rest}>
|
||||||
<CardProvider value={size}>{children}</CardProvider>
|
<CardProvider value={padding}>{children}</CardProvider>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,11 +23,12 @@ export type CardInsetProps = ComponentProps<"div"> & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Content that bleeds past the Card's padding to its edges (Radix "Inset"),
|
// Content that bleeds past the Card's padding to its edges (Radix "Inset"),
|
||||||
// e.g. a cover image. Negates the padding for the parent Card's size, read from
|
// e.g. a cover image. Negates the parent Card's resolved padding (read from
|
||||||
// context, so it always lines up.
|
// context) so it always lines up, even when padding is decoupled from size. With
|
||||||
|
// `padding="none"` there is nothing to negate, so this is a no-op wrapper.
|
||||||
export function CardInset(props: CardInsetProps) {
|
export function CardInset(props: CardInsetProps) {
|
||||||
const { side = "all", className, ...rest } = props;
|
const { side = "all", className, ...rest } = props;
|
||||||
const size = useCardContext();
|
const padding = useCardContext();
|
||||||
|
|
||||||
return <div className={cardInset({ size, side, className })} {...rest} />;
|
return <div className={cardInset({ padding, side, className })} {...rest} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,11 @@
|
|||||||
import { createContext, useContext } from "react";
|
import { createContext, useContext } from "react";
|
||||||
|
|
||||||
export type CardSize = 1 | 2 | 3 | 4 | 5;
|
export type CardSize = 1 | 2 | 3 | 4 | 5;
|
||||||
|
export type CardPadding = CardSize | "none";
|
||||||
|
|
||||||
// The card's size, shared with CardInset so it can negate the matching padding.
|
// The card's resolved padding, shared with CardInset so it negates the actual
|
||||||
const CardContext = createContext<CardSize>(1);
|
// padding (which can be decoupled from `size` via the `padding` prop).
|
||||||
|
const CardContext = createContext<CardPadding>(1);
|
||||||
|
|
||||||
export const CardProvider = CardContext.Provider;
|
export const CardProvider = CardContext.Provider;
|
||||||
|
|
||||||
|
|||||||
@@ -63,43 +63,44 @@ export const card = tv({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Negates the card's padding so content bleeds to its edges. The negative
|
// Negates the card's padding so content bleeds to its edges. The negative
|
||||||
// margin must match the card's size padding (1→3, 2→4, 3→5, 4→6, 5→8), so the
|
// margin must match the card's resolved padding (1→3, 2→4, 3→5, 4→6, 5→8), so
|
||||||
// matrix is keyed on both size and side. `overflow-hidden` clips bled media.
|
// the matrix is keyed on the padding (from context) and side. `padding="none"`
|
||||||
|
// has nothing to negate. `overflow-hidden` clips bled media.
|
||||||
export const cardInset = tv({
|
export const cardInset = tv({
|
||||||
base: "overflow-hidden",
|
base: "overflow-hidden",
|
||||||
variants: {
|
variants: {
|
||||||
size: { 1: "", 2: "", 3: "", 4: "", 5: "" },
|
padding: { 1: "", 2: "", 3: "", 4: "", 5: "", none: "" },
|
||||||
side: { all: "", x: "", y: "", top: "", bottom: "" },
|
side: { all: "", x: "", y: "", top: "", bottom: "" },
|
||||||
},
|
},
|
||||||
compoundVariants: [
|
compoundVariants: [
|
||||||
{ size: 1, side: "all", class: "-m-3" },
|
{ padding: 1, side: "all", class: "-m-3" },
|
||||||
{ size: 1, side: "x", class: "-mx-3" },
|
{ padding: 1, side: "x", class: "-mx-3" },
|
||||||
{ size: 1, side: "y", class: "-my-3" },
|
{ padding: 1, side: "y", class: "-my-3" },
|
||||||
{ size: 1, side: "top", class: "-mx-3 -mt-3 mb-3" },
|
{ padding: 1, side: "top", class: "-mx-3 -mt-3 mb-3" },
|
||||||
{ size: 1, side: "bottom", class: "-mx-3 -mb-3 mt-3" },
|
{ padding: 1, side: "bottom", class: "-mx-3 -mb-3 mt-3" },
|
||||||
{ size: 2, side: "all", class: "-m-4" },
|
{ padding: 2, side: "all", class: "-m-4" },
|
||||||
{ size: 2, side: "x", class: "-mx-4" },
|
{ padding: 2, side: "x", class: "-mx-4" },
|
||||||
{ size: 2, side: "y", class: "-my-4" },
|
{ padding: 2, side: "y", class: "-my-4" },
|
||||||
{ size: 2, side: "top", class: "-mx-4 -mt-4 mb-4" },
|
{ padding: 2, side: "top", class: "-mx-4 -mt-4 mb-4" },
|
||||||
{ size: 2, side: "bottom", class: "-mx-4 -mb-4 mt-4" },
|
{ padding: 2, side: "bottom", class: "-mx-4 -mb-4 mt-4" },
|
||||||
{ size: 3, side: "all", class: "-m-5" },
|
{ padding: 3, side: "all", class: "-m-5" },
|
||||||
{ size: 3, side: "x", class: "-mx-5" },
|
{ padding: 3, side: "x", class: "-mx-5" },
|
||||||
{ size: 3, side: "y", class: "-my-5" },
|
{ padding: 3, side: "y", class: "-my-5" },
|
||||||
{ size: 3, side: "top", class: "-mx-5 -mt-5 mb-5" },
|
{ padding: 3, side: "top", class: "-mx-5 -mt-5 mb-5" },
|
||||||
{ size: 3, side: "bottom", class: "-mx-5 -mb-5 mt-5" },
|
{ padding: 3, side: "bottom", class: "-mx-5 -mb-5 mt-5" },
|
||||||
{ size: 4, side: "all", class: "-m-6" },
|
{ padding: 4, side: "all", class: "-m-6" },
|
||||||
{ size: 4, side: "x", class: "-mx-6" },
|
{ padding: 4, side: "x", class: "-mx-6" },
|
||||||
{ size: 4, side: "y", class: "-my-6" },
|
{ padding: 4, side: "y", class: "-my-6" },
|
||||||
{ size: 4, side: "top", class: "-mx-6 -mt-6 mb-6" },
|
{ padding: 4, side: "top", class: "-mx-6 -mt-6 mb-6" },
|
||||||
{ size: 4, side: "bottom", class: "-mx-6 -mb-6 mt-6" },
|
{ padding: 4, side: "bottom", class: "-mx-6 -mb-6 mt-6" },
|
||||||
{ size: 5, side: "all", class: "-m-8" },
|
{ padding: 5, side: "all", class: "-m-8" },
|
||||||
{ size: 5, side: "x", class: "-mx-8" },
|
{ padding: 5, side: "x", class: "-mx-8" },
|
||||||
{ size: 5, side: "y", class: "-my-8" },
|
{ padding: 5, side: "y", class: "-my-8" },
|
||||||
{ size: 5, side: "top", class: "-mx-8 -mt-8 mb-8" },
|
{ padding: 5, side: "top", class: "-mx-8 -mt-8 mb-8" },
|
||||||
{ size: 5, side: "bottom", class: "-mx-8 -mb-8 mt-8" },
|
{ padding: 5, side: "bottom", class: "-mx-8 -mb-8 mt-8" },
|
||||||
],
|
],
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
size: 1,
|
padding: 1,
|
||||||
side: "all",
|
side: "all",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,7 +19,10 @@ import { ProboLogo } from "./ProboLogo";
|
|||||||
export default {
|
export default {
|
||||||
title: "v2/ProboLogo",
|
title: "v2/ProboLogo",
|
||||||
component: ProboLogo,
|
component: ProboLogo,
|
||||||
render: () => <ProboLogo className="h-6 w-auto text-sand-12" />,
|
args: {
|
||||||
|
className: "h-6 w-auto text-sand-12",
|
||||||
|
},
|
||||||
|
render: args => <ProboLogo {...args} />,
|
||||||
} satisfies Meta<typeof ProboLogo>;
|
} satisfies Meta<typeof ProboLogo>;
|
||||||
|
|
||||||
type Story = StoryObj<typeof ProboLogo>;
|
type Story = StoryObj<typeof ProboLogo>;
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export function ProboLogo(props: ProboLogoProps) {
|
|||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 90 24"
|
viewBox="0 0 90 24"
|
||||||
role="img"
|
role="img"
|
||||||
|
aria-label="Probo"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<mask id={maskId} maskUnits="userSpaceOnUse">
|
<mask id={maskId} maskUnits="userSpaceOnUse">
|
||||||
|
|||||||
Reference in New Issue
Block a user