@varialkit/banner 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs.md ADDED
@@ -0,0 +1,53 @@
1
+ # Banner
2
+
3
+ Banners highlight important information inline within a page. Use them for status messages, policy updates, or
4
+ contextual guidance that should remain visible.
5
+
6
+ ## Usage
7
+
8
+ ```tsx
9
+ import { Banner } from "@solara/banner";
10
+
11
+ export function Example() {
12
+ return (
13
+ <Banner
14
+ variant="info"
15
+ title="Heads up"
16
+ description="We have updated our terms of service."
17
+ />
18
+ );
19
+ }
20
+ ```
21
+
22
+ ## Props
23
+
24
+ | Prop | Type | Default | Description |
25
+ | --- | --- | --- | --- |
26
+ | `title` | `string` | — | Headline text for the banner. |
27
+ | `description` | `string` | — | Supporting description text. |
28
+ | `variant` | `"neutral" | "info" | "success" | "warning" | "destructive"` | `"neutral"` | Visual tone of the banner. |
29
+ | `icon` | `ReactNode` | — | Optional icon rendered before the content. |
30
+ | `iconName` | `SolaraIconName | IconProps` | — | Optional icon name or props for the leading icon. |
31
+ | `showIcon` | `boolean` | `true` | Toggle the leading icon on or off. |
32
+ | `action` | `ReactNode` | — | Optional action node rendered on the right. |
33
+ | `actions` | `ReactNode` | — | Optional action nodes rendered on the right (preferred). |
34
+ | `onDismiss` | `() => void` | — | Shows a dismiss button when provided. |
35
+ | `showTitle` | `boolean` | `true` | Toggles the title visibility. |
36
+ | `showDescription` | `boolean` | `true` | Toggles the description visibility. |
37
+ | `showAccentBar` | `boolean` | `true` | Toggles the left accent bar. |
38
+ | `radius` | `"default" | "none" | "full"` | `"default"` | Controls the banner corner radius. |
39
+ | `fullWidth` | `boolean` | `false` | Stretches the banner to full width. |
40
+
41
+ Banner also accepts standard `div` props such as `className` and `role`.
42
+
43
+ ## Icons
44
+
45
+ You can pass an icon name or full icon props. Icons inherit the banner text color.
46
+
47
+ ```tsx
48
+ <Banner
49
+ title="Heads up"
50
+ description="We have updated our terms of service."
51
+ iconName="data_spreadsheet_search_24"
52
+ />
53
+ ```
package/examples.tsx ADDED
@@ -0,0 +1,195 @@
1
+ import React from "react";
2
+ import { iconNames } from "@solara/icons";
3
+ import type { SolaraIconName } from "@solara/icons";
4
+ import { Button } from "@solara/button";
5
+ import { Banner } from "./src/Banner";
6
+ import type { BannerProps } from "./src/Banner.types";
7
+
8
+ const DemoAction = ({ label = "Review" }: { label?: string }) => (
9
+ <Button label={label} variant="ghost" size="small" />
10
+ );
11
+
12
+ export const stories = {
13
+ playground: {
14
+ title: "Playground",
15
+ description: "Tweak the props to explore the Banner API.",
16
+ render: (
17
+ props: BannerProps & {
18
+ showIcon?: boolean;
19
+ showAction?: boolean;
20
+ dismissible?: boolean;
21
+ }
22
+ ) => {
23
+ const {
24
+ showIcon = false,
25
+ showAction = false,
26
+ dismissible = false,
27
+ ...bannerProps
28
+ } = props;
29
+
30
+ return (
31
+ <Banner
32
+ {...bannerProps}
33
+ iconName={(props.iconName as SolaraIconName) || undefined}
34
+ showIcon={showIcon}
35
+ actions={
36
+ showAction ? (
37
+ <>
38
+ <DemoAction label="Review" />
39
+ <DemoAction label="Later" />
40
+ </>
41
+ ) : undefined
42
+ }
43
+ onDismiss={dismissible ? () => undefined : undefined}
44
+ />
45
+ );
46
+ },
47
+ controls: [
48
+ { name: "title", type: "text" },
49
+ { name: "description", type: "text" },
50
+ { name: "showTitle", type: "boolean", label: "Show title" },
51
+ { name: "showDescription", type: "boolean", label: "Show description" },
52
+ { name: "showAccentBar", type: "boolean", label: "Show accent bar" },
53
+ {
54
+ name: "variant",
55
+ type: "select",
56
+ options: ["neutral", "info", "success", "warning", "destructive"],
57
+ },
58
+ {
59
+ name: "radius",
60
+ type: "select",
61
+ options: ["default", "none", "full"],
62
+ },
63
+ { name: "showIcon", type: "boolean", label: "Show icon" },
64
+ {
65
+ name: "iconName",
66
+ label: "Icon Name",
67
+ type: "select",
68
+ options: ["", ...iconNames],
69
+ },
70
+ { name: "showAction", type: "boolean", label: "Show action" },
71
+ { name: "dismissible", type: "boolean", label: "Dismissible" },
72
+ { name: "fullWidth", type: "boolean" },
73
+ ],
74
+ initialProps: {
75
+ title: "New policy update",
76
+ description: "Please review the latest guidelines before continuing.",
77
+ showTitle: true,
78
+ showDescription: true,
79
+ showAccentBar: true,
80
+ variant: "neutral",
81
+ radius: "default",
82
+ showIcon: false,
83
+ iconName: "",
84
+ showAction: false,
85
+ dismissible: false,
86
+ fullWidth: false,
87
+ },
88
+ },
89
+ variants: {
90
+ title: "Variants",
91
+ showProps: false,
92
+ render: () => (
93
+ <div style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
94
+ <Banner title="Neutral" description="Default informational messaging." />
95
+ <Banner
96
+ title="Info"
97
+ description="Share helpful context and details."
98
+ variant="info"
99
+ iconName="data_spreadsheet_search_24"
100
+ />
101
+ <Banner
102
+ title="Success"
103
+ description="Confirm that an action completed successfully."
104
+ variant="success"
105
+ iconName="arrow_line_up_16"
106
+ />
107
+ <Banner
108
+ title="Warning"
109
+ description="Let users know about a potential issue."
110
+ variant="warning"
111
+ iconName="arrow_chevron_down_16"
112
+ />
113
+ <Banner
114
+ title="Destructive"
115
+ description="Call attention to errors or blocking issues."
116
+ variant="destructive"
117
+ iconName="arrow_swap_16"
118
+ />
119
+ </div>
120
+ ),
121
+ code: `import { Banner } from "@solara/banner";
122
+
123
+ export function Example() {
124
+ return (
125
+ <div style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
126
+ <Banner title="Neutral" description="Default informational messaging." />
127
+ <Banner
128
+ title="Info"
129
+ description="Share helpful context and details."
130
+ variant="info"
131
+ iconName="data_spreadsheet_search_24"
132
+ />
133
+ <Banner
134
+ title="Success"
135
+ description="Confirm that an action completed successfully."
136
+ variant="success"
137
+ iconName="arrow_line_up_16"
138
+ />
139
+ <Banner
140
+ title="Warning"
141
+ description="Let users know about a potential issue."
142
+ variant="warning"
143
+ iconName="arrow_chevron_down_16"
144
+ />
145
+ <Banner
146
+ title="Destructive"
147
+ description="Call attention to errors or blocking issues."
148
+ variant="destructive"
149
+ iconName="arrow_swap_16"
150
+ />
151
+ </div>
152
+ );
153
+ }
154
+ `,
155
+ },
156
+ actions: {
157
+ title: "With Action",
158
+ description: "Banners can include a call to action and dismiss control.",
159
+ showProps: false,
160
+ render: () => (
161
+ <Banner
162
+ title="Finish setup"
163
+ description="Verify your email to complete onboarding."
164
+ variant="info"
165
+ actions={
166
+ <>
167
+ <Button label="Verify" variant="ghost" size="small" />
168
+ <Button label="Remind me" variant="ghost" size="small" />
169
+ </>
170
+ }
171
+ onDismiss={() => undefined}
172
+ />
173
+ ),
174
+ code: `import { Banner } from "@solara/banner";
175
+ import { Button } from "@solara/button";
176
+
177
+ export function Example() {
178
+ return (
179
+ <Banner
180
+ title="Finish setup"
181
+ description="Verify your email to complete onboarding."
182
+ variant="info"
183
+ actions={
184
+ <>
185
+ <Button label="Verify" variant="ghost" size="small" />
186
+ <Button label="Remind me" variant="ghost" size="small" />
187
+ </>
188
+ }
189
+ onDismiss={() => undefined}
190
+ />
191
+ );
192
+ }
193
+ `,
194
+ },
195
+ };
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@varialkit/banner",
3
+ "version": "0.1.1",
4
+ "type": "module",
5
+ "main": "src/index.ts",
6
+ "types": "src/index.ts",
7
+ "exports": {
8
+ ".": "./src/index.ts",
9
+ "./examples": "./examples.tsx"
10
+ },
11
+ "dependencies": {
12
+ "@varialkit/icons": "0.1.1",
13
+ "@varialkit/button": "0.1.1"
14
+ },
15
+ "files": [
16
+ "src",
17
+ "docs.md",
18
+ "examples.tsx"
19
+ ],
20
+ "peerDependencies": {
21
+ "react": "^19.0.0"
22
+ },
23
+ "devDependencies": {
24
+ "@types/react": "19.0.10",
25
+ "react": "19.0.0"
26
+ }
27
+ }
@@ -0,0 +1,171 @@
1
+ .solara-banner {
2
+ --banner-bg: var(--color-surface-100);
3
+ --banner-border: var(--color-divider-secondary);
4
+ --banner-title: var(--color-text-primary);
5
+ --banner-description: var(--color-text-secondary);
6
+ --banner-icon: var(--color-icon-secondary);
7
+ --banner-accent: var(--color-divider-secondary);
8
+ --banner-radius: var(--radius-3);
9
+
10
+ position: relative;
11
+ display: flex;
12
+ align-items: center;
13
+ gap: calc(var(--space-3) * var(--spacing-multiplier));
14
+ padding: calc(var(--space-3) * var(--spacing-multiplier))
15
+ calc(var(--space-4) * var(--spacing-multiplier));
16
+ border-radius: var(--banner-radius);
17
+ border: 1px solid var(--banner-border);
18
+ overflow: hidden;
19
+ background-color: var(--banner-bg);
20
+ color: var(--banner-title);
21
+ font-family: var(--font-body);
22
+ }
23
+
24
+ .solara-banner::before {
25
+ content: "";
26
+ position: absolute;
27
+ inset: 0 auto 0 0;
28
+ width: 4px;
29
+ background-color: var(--banner-accent);
30
+ }
31
+
32
+ .solara-banner--neutral {
33
+ --banner-bg: var(--color-surface-100);
34
+ --banner-border: var(--color-divider-secondary);
35
+ --banner-title: var(--color-text-primary);
36
+ --banner-description: var(--color-text-secondary);
37
+ --banner-icon: var(--color-icon-secondary);
38
+ --banner-accent: var(--color-divider-secondary);
39
+ }
40
+
41
+ .solara-banner--info {
42
+ --banner-bg: var(--color-surface-info);
43
+ --banner-border: var(--color-divider-accent);
44
+ --banner-title: var(--color-text-info);
45
+ --banner-description: var(--color-text-info);
46
+ --banner-icon: var(--color-icon-info);
47
+ --banner-accent: var(--color-surface-status-bold-info);
48
+ }
49
+
50
+ .solara-banner--success {
51
+ --banner-bg: var(--color-surface-success);
52
+ --banner-border: var(--color-divider-secondary);
53
+ --banner-title: var(--color-text-success);
54
+ --banner-description: var(--color-text-success);
55
+ --banner-icon: var(--color-icon-success);
56
+ --banner-accent: var(--color-surface-status-bold-success);
57
+ }
58
+
59
+ .solara-banner--warning {
60
+ --banner-bg: var(--color-surface-warning);
61
+ --banner-border: var(--color-divider-secondary);
62
+ --banner-title: var(--color-text-warning);
63
+ --banner-description: var(--color-text-warning);
64
+ --banner-icon: var(--color-icon-warning);
65
+ --banner-accent: var(--color-surface-status-bold-warning);
66
+ }
67
+
68
+ .solara-banner--destructive {
69
+ --banner-bg: var(--color-surface-destructive);
70
+ --banner-border: var(--color-divider-alert);
71
+ --banner-title: var(--color-text-alert);
72
+ --banner-description: var(--color-text-alert);
73
+ --banner-icon: var(--color-icon-alert);
74
+ --banner-accent: var(--color-surface-status-bold-destructive);
75
+ }
76
+
77
+ .solara-banner--radius-default {
78
+ --banner-radius: var(--radius-3);
79
+ }
80
+
81
+ .solara-banner--radius-none {
82
+ --banner-radius: var(--radius-0);
83
+ }
84
+
85
+ .solara-banner--radius-full {
86
+ --banner-radius: var(--radius-pill);
87
+ }
88
+
89
+ .solara-banner--full-width {
90
+ width: 100%;
91
+ }
92
+
93
+ .solara-banner--no-accent {
94
+ border-left: 1px solid var(--banner-border);
95
+ }
96
+
97
+ .solara-banner--no-accent::before {
98
+ display: none;
99
+ }
100
+
101
+ .solara-banner__icon {
102
+ display: flex;
103
+ align-items: center;
104
+ justify-content: center;
105
+ width: calc(var(--space-5) * var(--spacing-multiplier));
106
+ height: calc(var(--space-5) * var(--spacing-multiplier));
107
+ color: var(--banner-icon);
108
+ flex-shrink: 0;
109
+ }
110
+
111
+ .solara-banner__icon .solara-icon [stroke]:not([stroke="none"]) {
112
+ stroke: currentColor;
113
+ }
114
+
115
+ .solara-banner__icon .solara-icon [fill]:not([fill="none"]) {
116
+ fill: currentColor;
117
+ }
118
+
119
+ .solara-banner__content {
120
+ flex: 1;
121
+ min-width: 0;
122
+ }
123
+
124
+ .solara-banner__title {
125
+ font-size: var(--font-size-body-scaled);
126
+ line-height: var(--line-height-body-scaled);
127
+ font-weight: 600;
128
+ margin-bottom: calc(var(--space-1) * var(--spacing-multiplier));
129
+ color: var(--banner-title);
130
+ }
131
+
132
+ .solara-banner__description {
133
+ font-size: var(--font-size-body-scaled);
134
+ line-height: var(--line-height-body-scaled);
135
+ color: var(--banner-description);
136
+ }
137
+
138
+ .solara-banner__end {
139
+ display: flex;
140
+ align-items: center;
141
+ gap: calc(var(--space-2) * var(--spacing-multiplier));
142
+ margin-left: auto;
143
+ align-self: center;
144
+ }
145
+
146
+ .solara-banner__action {
147
+ display: flex;
148
+ align-items: center;
149
+ }
150
+
151
+ .solara-banner__dismiss {
152
+ display: inline-flex;
153
+ align-items: center;
154
+ justify-content: center;
155
+ width: calc(var(--space-6) * var(--spacing-multiplier));
156
+ height: calc(var(--space-6) * var(--spacing-multiplier));
157
+ border: none;
158
+ border-radius: var(--radius-2);
159
+ background: transparent;
160
+ color: var(--banner-icon);
161
+ cursor: pointer;
162
+ opacity: 0.7;
163
+ transition: opacity 0.2s ease, background-color 0.2s ease;
164
+ }
165
+
166
+ .solara-banner__dismiss:hover,
167
+ .solara-banner__dismiss:focus-visible {
168
+ opacity: 1;
169
+ background-color: var(--color-surface-200);
170
+ outline: none;
171
+ }
package/src/Banner.tsx ADDED
@@ -0,0 +1,111 @@
1
+ import React, { forwardRef } from "react";
2
+ import { Icon } from "@solara/icons";
3
+ import type { IconProps } from "@solara/icons";
4
+ import type { BannerProps, BannerVariant } from "./Banner.types";
5
+ import "./Banner.scss";
6
+
7
+ const alertVariants = new Set<BannerVariant>(["warning", "destructive"]);
8
+
9
+ type BannerIcon = IconProps | IconProps["name"];
10
+
11
+ const normalizeIconProps = (icon: BannerIcon): IconProps =>
12
+ typeof icon === "string" ? { name: icon } : icon;
13
+
14
+ const resolveIconProps = (icon: BannerIcon): IconProps => {
15
+ const iconProps = normalizeIconProps(icon);
16
+
17
+ return {
18
+ ...iconProps,
19
+ style: {
20
+ ...iconProps.style,
21
+ color: "currentColor",
22
+ },
23
+ };
24
+ };
25
+
26
+ /**
27
+ * Banner component for prominent, inline messaging.
28
+ */
29
+ export const Banner = forwardRef<HTMLDivElement, BannerProps>(
30
+ (
31
+ {
32
+ title,
33
+ description,
34
+ icon,
35
+ iconName,
36
+ showIcon = true,
37
+ action,
38
+ actions,
39
+ onDismiss,
40
+ dismissLabel = "Dismiss",
41
+ showTitle = true,
42
+ showDescription = true,
43
+ showAccentBar = true,
44
+ variant = "neutral",
45
+ radius = "default",
46
+ fullWidth = false,
47
+ className,
48
+ children,
49
+ role,
50
+ ...props
51
+ },
52
+ ref
53
+ ) => {
54
+ const resolvedRole = role ?? (alertVariants.has(variant) ? "alert" : "status");
55
+
56
+ const classes = [
57
+ "solara-banner",
58
+ `solara-banner--${variant}`,
59
+ `solara-banner--radius-${radius}`,
60
+ showAccentBar ? "solara-banner--accent" : "solara-banner--no-accent",
61
+ fullWidth ? "solara-banner--full-width" : null,
62
+ className,
63
+ ]
64
+ .filter(Boolean)
65
+ .join(" ");
66
+
67
+ const resolvedActions = actions ?? action;
68
+ const resolvedIcon =
69
+ showIcon === false
70
+ ? null
71
+ : icon
72
+ ? icon
73
+ : iconName
74
+ ? <Icon {...resolveIconProps(iconName)} />
75
+ : null;
76
+
77
+ return (
78
+ <div ref={ref} className={classes} role={resolvedRole} {...props}>
79
+ {resolvedIcon ? <div className="solara-banner__icon">{resolvedIcon}</div> : null}
80
+ <div className="solara-banner__content">
81
+ {title && showTitle ? (
82
+ <div className="solara-banner__title">{title}</div>
83
+ ) : null}
84
+ {description && showDescription ? (
85
+ <div className="solara-banner__description">{description}</div>
86
+ ) : null}
87
+ {children}
88
+ </div>
89
+ {resolvedActions || onDismiss ? (
90
+ <div className="solara-banner__end">
91
+ {resolvedActions ? (
92
+ <div className="solara-banner__action">{resolvedActions}</div>
93
+ ) : null}
94
+ {onDismiss ? (
95
+ <button
96
+ type="button"
97
+ className="solara-banner__dismiss"
98
+ onClick={onDismiss}
99
+ aria-label={dismissLabel}
100
+ >
101
+ <span aria-hidden="true">×</span>
102
+ </button>
103
+ ) : null}
104
+ </div>
105
+ ) : null}
106
+ </div>
107
+ );
108
+ }
109
+ );
110
+
111
+ Banner.displayName = "Banner";
@@ -0,0 +1,38 @@
1
+ import type React from "react";
2
+ import type { IconProps } from "@solara/icons";
3
+
4
+ export type BannerVariant = "neutral" | "info" | "success" | "warning" | "destructive";
5
+ export type BannerRadius = "default" | "none" | "full";
6
+
7
+ export type BannerProps = React.HTMLAttributes<HTMLDivElement> & {
8
+ /** Headline text for the banner. */
9
+ title?: string;
10
+ /** Supporting description text. */
11
+ description?: string;
12
+ /** Optional icon to show before the content. */
13
+ icon?: React.ReactNode;
14
+ /** Optional icon name or props for the leading icon. */
15
+ iconName?: IconProps | IconProps["name"];
16
+ /** Toggle the leading icon on or off. */
17
+ showIcon?: boolean;
18
+ /** Optional action node rendered on the right. */
19
+ action?: React.ReactNode;
20
+ /** Optional action nodes rendered on the right (preferred over `action`). */
21
+ actions?: React.ReactNode;
22
+ /** Callback when the dismiss button is clicked. */
23
+ onDismiss?: () => void;
24
+ /** Accessible label for the dismiss button. */
25
+ dismissLabel?: string;
26
+ /** Whether to show the title text. */
27
+ showTitle?: boolean;
28
+ /** Whether to show the description text. */
29
+ showDescription?: boolean;
30
+ /** Whether to show the left accent bar. */
31
+ showAccentBar?: boolean;
32
+ /** Visual tone of the banner. */
33
+ variant?: BannerVariant;
34
+ /** Corner radius for the banner. */
35
+ radius?: BannerRadius;
36
+ /** Stretch the banner to full width. */
37
+ fullWidth?: boolean;
38
+ };
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { Banner } from "./Banner";
2
+ export type { BannerProps, BannerVariant, BannerRadius } from "./Banner.types";