Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
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
22 changes: 22 additions & 0 deletions design-system/_ds_manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@
"name": "Spinner",
"sourcePath": "components/feedback/Spinner.jsx"
},
{
"name": "StatusCallout",
"sourcePath": "components/feedback/StatusCallout.jsx"
},
{
"name": "StatusPill",
"sourcePath": "components/feedback/StatusPill.jsx"
Expand Down Expand Up @@ -612,6 +616,24 @@
"kind": "color",
"definedIn": "tokens/colors.css"
},
{
"name": "--success-soft",
"value": "rgba(52, 211, 153, 0.12)",
"kind": "color",
"definedIn": "tokens/colors.css"
},
{
"name": "--warning-soft",
"value": "rgba(245, 166, 35, 0.12)",
"kind": "color",
"definedIn": "tokens/colors.css"
},
{
"name": "--error-soft",
"value": "rgba(243, 105, 127, 0.12)",
"kind": "color",
"definedIn": "tokens/colors.css"
},
{
"name": "--shadow",
"value": "rgba(0, 0, 0, 0.45)",
Expand Down
22 changes: 22 additions & 0 deletions design-system/components/feedback/StatusCallout.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { CSSProperties, ReactNode } from "react";

export type StatusCalloutTone = "error" | "warning" | "success" | "info";

export interface StatusCalloutProps {
/** Severity tone. Drives the tint, border, icon color, and default a11y role. */
tone?: StatusCalloutTone;
/** Decorative leading glyph/icon rendered in a soft chip (aria-hidden). */
icon?: ReactNode;
/** Emphasized title line. */
title?: ReactNode;
/** Body copy. */
children?: ReactNode;
/** Action row (buttons/links) rendered under the body. */
actions?: ReactNode;
/** Override the ARIA role (defaults: `alert` for error, `status` otherwise). */
role?: string;
style?: CSSProperties;
[key: string]: unknown;
}

export declare function StatusCallout(props: StatusCalloutProps): JSX.Element;
108 changes: 108 additions & 0 deletions design-system/components/feedback/StatusCallout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import React from "react";

/**
* StatusCallout — a soft-tinted inline banner (icon + title + text + actions)
* for warn / error / info / success states. Larger and more structured than the
* inline `Alert`: it carries a rounded icon chip, an emphasized title, body copy,
* and a row of actions, and is used to surface fail-closed guards and load errors.
*
* Design-system-first: every color/spacing/type value is a token. RTL-safe via
* CSS logical properties (gap + logical padding), so it mirrors automatically.
*
* A11y: defaults `role` by tone — `alert` for errors (assertively announced),
* `status` for the rest. The icon is decorative (`aria-hidden`); meaning lives in
* the title + text. Pass an explicit `role`/`aria-label` to override.
*/
const TONES = {
error: { color: "var(--error-color)", soft: "var(--error-soft)", role: "alert" },
warning: { color: "var(--warning-color)", soft: "var(--warning-soft)", role: "status" },
success: { color: "var(--success-color)", soft: "var(--success-soft)", role: "status" },
info: { color: "var(--accent-color)", soft: "var(--accent-soft)", role: "status" },
};

export function StatusCallout({
tone = "info",
icon,
title,
children,
actions,
role,
style,
...rest
}) {
const t = TONES[tone] || TONES.info;
return (
<div
role={role ?? t.role}
style={{
display: "flex",
gap: "var(--space-4)",
alignItems: "flex-start",
background: t.soft,
border: `1px solid ${t.color}`,
borderRadius: "var(--radius-md)",
padding: "var(--space-4) var(--space-5)",
fontFamily: "var(--font-sans)",
...style,
}}
{...rest}
>
{icon != null && (
<span
aria-hidden="true"
style={{
flex: "none",
width: "28px",
height: "28px",
borderRadius: "var(--radius-pill)",
display: "grid",
placeItems: "center",
background: t.soft,
color: t.color,
fontSize: "var(--text-md)",
}}
>
{icon}
</span>
)}
<div style={{ flex: 1, minWidth: 0 }}>
{title && (
<p
style={{
margin: "0 0 var(--space-1)",
fontWeight: "var(--weight-semibold)",
fontSize: "var(--text-md)",
color: "var(--text-primary)",
}}
>
{title}
</p>
)}
{children != null && (
<p
style={{
margin: 0,
color: "var(--text-secondary)",
fontSize: "var(--text-sm)",
lineHeight: "var(--leading-normal, 1.5)",
}}
>
{children}
</p>
)}
{actions != null && (
<div
style={{
marginTop: "var(--space-3)",
display: "flex",
flexWrap: "wrap",
gap: "var(--space-3)",
}}
>
{actions}
</div>
)}
</div>
</div>
);
}
2 changes: 2 additions & 0 deletions design-system/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export { Skeleton } from './components/feedback/Skeleton'
export type * from './components/feedback/Skeleton'
export { Spinner } from './components/feedback/Spinner'
export type * from './components/feedback/Spinner'
export { StatusCallout } from './components/feedback/StatusCallout'
export type * from './components/feedback/StatusCallout'
export { StatusPill } from './components/feedback/StatusPill'
export type * from './components/feedback/StatusPill'
export { Toast } from './components/feedback/Toast'
Expand Down
1 change: 1 addition & 0 deletions design-system/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export { EmptyState } from './components/feedback/EmptyState.jsx'
export { Rating } from './components/feedback/Rating.jsx'
export { Skeleton } from './components/feedback/Skeleton.jsx'
export { Spinner } from './components/feedback/Spinner.jsx'
export { StatusCallout } from './components/feedback/StatusCallout.jsx'
export { StatusPill } from './components/feedback/StatusPill.jsx'
export { Toast } from './components/feedback/Toast.jsx'
export { FieldStatus } from './components/forms/FieldStatus.jsx'
Expand Down
7 changes: 7 additions & 0 deletions design-system/tokens/colors.css
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@
--success-color: var(--mint-500);
--warning-color: var(--amber-500);
--error-color: var(--coral-500);
/* status soft-tints — inline callout / banner fills (StatusCallout, posture summary) */
--success-soft: rgba(52, 211, 153, 0.12);
--warning-soft: rgba(245, 166, 35, 0.12);
--error-soft: rgba(243, 105, 127, 0.12);
/* elevation */
--shadow: rgba(0, 0, 0, 0.45);
/* modal / dialog scrim — dims the shell behind overlays */
Expand Down Expand Up @@ -129,6 +133,9 @@
--success-color: var(--mint-600);
--warning-color: var(--amber-600);
--error-color: var(--coral-600);
--success-soft: rgba(16, 185, 129, 0.10);
--warning-soft: rgba(217, 131, 19, 0.10);
--error-soft: rgba(224, 85, 107, 0.10);
--shadow: rgba(15, 23, 42, 0.12);
--scrim: rgba(15, 23, 42, 0.32);
--seam: linear-gradient(90deg, var(--accent-color) 0%, var(--accent-2) 100%);
Expand Down
Loading
Loading