Skip to content

Commit df6a984

Browse files
committed
feat: add callout component with variant support
1 parent cf435cd commit df6a984

File tree

2 files changed

+205
-0
lines changed

2 files changed

+205
-0
lines changed
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
@use "@pythnetwork/component-library/theme";
2+
3+
.callout {
4+
display: flex;
5+
flex-direction: column;
6+
gap: theme.spacing(1);
7+
border-radius: theme.border-radius("2xl");
8+
text-decoration: none;
9+
color: unset;
10+
outline-offset: 0;
11+
outline: theme.spacing(1) solid transparent;
12+
transition-property: outline-color, border-color, box-shadow, background;
13+
transition-duration: 100ms;
14+
transition-timing-function: linear;
15+
border: 1px solid transparent;
16+
position: relative;
17+
padding: theme.spacing(1);
18+
isolation: isolate;
19+
-webkit-tap-highlight-color: transparent;
20+
21+
.hover {
22+
border-radius: theme.border-radius("2xl");
23+
position: absolute;
24+
inset: -1px;
25+
opacity: 0;
26+
transition: opacity 100ms linear;
27+
background: theme.color("button", "outline", "background", "hover");
28+
z-index: -1;
29+
}
30+
31+
.body {
32+
position: relative;
33+
display: inline-flex;
34+
flex-flow: row nowrap;
35+
gap: theme.spacing(3);
36+
align-items: center;
37+
padding: theme.spacing(3);
38+
39+
@include theme.text("lg", "medium");
40+
41+
.icon {
42+
font-size: theme.spacing(6);
43+
height: theme.spacing(6);
44+
}
45+
46+
}
47+
48+
&[data-variant="default"] {
49+
background: theme.color("states", "neutral", "background");
50+
color: theme.color("states", "neutral", "normal");
51+
52+
&[data-hovered] {
53+
@include theme.elevation("primary", 2);
54+
}
55+
}
56+
57+
&[data-variant="primary"] {
58+
background: theme.color("background", "primary");
59+
color: theme.color("foreground");
60+
61+
&[data-hovered] {
62+
@include theme.elevation("primary", 2);
63+
}
64+
}
65+
66+
&[data-variant="secondary"] {
67+
background: theme.color("background", "secondary");
68+
color: theme.color("foreground");
69+
70+
&[data-hovered] {
71+
@include theme.elevation("default", 2);
72+
}
73+
}
74+
75+
&[data-variant="tertiary"] {
76+
background: theme.color("background", "card-highlight");
77+
color: theme.color("foreground");
78+
79+
&[data-hovered] {
80+
@include theme.elevation("default", 2);
81+
}
82+
}
83+
84+
&[data-variant="error"] {
85+
background: theme.color("states", "error", "background");
86+
color: theme.color("states", "error", "color");
87+
88+
&[data-hovered] {
89+
@include theme.elevation("default", 2);
90+
}
91+
}
92+
93+
&[data-variant="info"] {
94+
background: theme.color("states", "info", "background");
95+
color: theme.color("states", "info", "normal");
96+
97+
&[data-hovered] {
98+
@include theme.elevation("default", 2);
99+
}
100+
}
101+
102+
&[data-variant="warning"] {
103+
background: theme.color("states", "warning", "background");
104+
color: theme.color("states", "warning", "normal");
105+
106+
&[data-hovered] {
107+
@include theme.elevation("default", 2);
108+
}
109+
}
110+
111+
&[data-variant="important"] {
112+
background: theme.color("states", "data", "background");
113+
color: theme.color("states", "data", "normal");
114+
115+
&[data-hovered] {
116+
@include theme.elevation("default", 2);
117+
}
118+
}
119+
120+
&[data-variant="success"] {
121+
background: theme.color("states", "success", "background");
122+
color: theme.color("states", "success", "base");
123+
124+
&[data-hovered] {
125+
@include theme.elevation("default", 2);
126+
}
127+
}
128+
129+
&[data-hovered] .hover {
130+
opacity: 1;
131+
}
132+
133+
&[data-focus-visible] {
134+
border-color: theme.color("focus");
135+
outline-color: theme.color("focus-dim");
136+
}
137+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"use client";
2+
3+
import { Link } from "@pythnetwork/component-library/unstyled/Link";
4+
import clsx from "clsx";
5+
import type { ComponentProps, ElementType, ReactNode } from "react";
6+
7+
import styles from "./index.module.scss";
8+
9+
export const VARIANTS = [
10+
"default",
11+
"primary",
12+
"secondary",
13+
"tertiary",
14+
"error",
15+
"info",
16+
"warning",
17+
"important",
18+
"success",
19+
] as const;
20+
21+
type OwnProps = {
22+
variant?: (typeof VARIANTS)[number] | undefined;
23+
icon?: ReactNode | undefined;
24+
nonInteractive?: boolean | undefined;
25+
};
26+
27+
export type Props<T extends ElementType> = Omit<
28+
ComponentProps<T>,
29+
keyof OwnProps
30+
> &
31+
OwnProps;
32+
33+
export const Callout = (
34+
props: (Props<"div"> & { nonInteractive?: true }) | Props<typeof Link>,
35+
) => {
36+
if (props.nonInteractive) {
37+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
38+
const { nonInteractive, ...otherProps } = props;
39+
return <div {...calloutProps(otherProps)} />;
40+
} else if ("href" in props) {
41+
return <Link {...calloutProps(props)} />;
42+
} else {
43+
return <div {...calloutProps(props)} />;
44+
}
45+
};
46+
47+
const calloutProps = <T extends ElementType>({
48+
className,
49+
variant = "default",
50+
children,
51+
icon,
52+
...props
53+
}: Props<T>) => ({
54+
...props,
55+
"data-variant": variant,
56+
className: clsx(styles.callout, className),
57+
children: (
58+
<>
59+
<div className={styles.hover} />
60+
<div className={styles.body}>
61+
{Boolean(icon) && <div className={styles.icon}>{icon}</div>}
62+
<div>
63+
{children}
64+
</div>
65+
</div>
66+
</>
67+
),
68+
});

0 commit comments

Comments
 (0)