diff --git a/.changeset/plenty-moons-shake.md b/.changeset/plenty-moons-shake.md
index 810ba9c..29d93b1 100644
--- a/.changeset/plenty-moons-shake.md
+++ b/.changeset/plenty-moons-shake.md
@@ -7,3 +7,13 @@ CodeBlock コンポーネントを追加し、Card と Anchor のスタイルを
- CodeBlock コンポーネントを新規追加。クリップボードコピー機能を内蔵し、シンタックスハイライト済みの React 要素を children で注入可能(shiki 等は非内包)
- Card の視覚変更: CodeBlock と同じ外観に統一(border を `--color-primary-2` に、padding 1rem を Card 本体に移動し各セクションの間隔は gap で管理)し、box-shadow を削除
- Anchor `variant="button"` の視覚変更: Button と統一(角丸を `calc(var(--size-radius) * 0.5)` に、フォーカスリングを outline 方式に、disabled 表現と transition を Button と同一に)
+
+AlertDialog / Drawer / Popover / Alert / Badge / Toast コンポーネントを追加
+
+- variables.css にセマンティックカラー変数(`--color-info` / `--color-success` / `--color-warning` / `--color-danger` とそれぞれの `-light` / `-dark`)を追加
+- Alert: タイトルと説明文を持つ静的表示コンポーネント。info / success / warning / danger の4バリアント
+- Badge: primary / secondary と状態色4種の計6バリアントを持つバッジ
+- AlertDialog: `role="alertdialog"` 固定のダイアログ。既存の Modal / DialogTrigger と組み合わせて使用
+- Drawer: placement で4方向(left / right / top / bottom)のスライドインに対応。dismissable prop で外側クリック時の挙動を制御
+- Popover: react-aria の Popover をラップし、非モーダル Dialog と矢印(OverlayArrow)を内蔵。placement / showArrow でカスタマイズ可能
+- Toast: react-aria の UNSTABLE\_ Toast API をラップ。`toast.show` / `info` / `success` / `warning` / `danger` / `close` の関数 API と、4隅に配置可能な ToastRegion を提供
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c737764..dda5288 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,29 @@
# Changelog
+## v0.14.0
+
+- feat: AlertDialog / Drawer / Popover / Alert / Badge / Toast コンポーネントを追加
+- feat: セマンティックカラー変数(`--color-info` / `--color-success` / `--color-warning` / `--color-danger`)を追加
+- feat: CodeBlock コンポーネントを追加
+- improve: Card と Anchor のスタイルを CodeBlock / Button と統一
+
+## v0.13.3
+
+- chore: changesets の fixed 設定で `@ginga-ui/core`・`@ginga-ui/utils`・`ginga-ui.com` のバージョンを 0.13.3 に統一
+- fix: tsdown を unbundle 化し、`"use client"` バナーの保持と CSS の自動付随に対応
+
+## v0.12.2
+
+- change: ビルドツールを tsup から tsdown(Rolldown ベース)へ移行
+
+## v0.12.1
+
+- fix: デプロイワークフローを修正
+
+## v0.12.0
+
+- change: パッケージスキーマを変更
+
## v0.11.0
- remove: CSS Modules
diff --git a/app/ginga-ui.com/src/app/components/alert-dialog/page.mdx b/app/ginga-ui.com/src/app/components/alert-dialog/page.mdx
new file mode 100644
index 0000000..ee5491e
--- /dev/null
+++ b/app/ginga-ui.com/src/app/components/alert-dialog/page.mdx
@@ -0,0 +1,18 @@
+import { ComponentDoc, Example } from "#/components/component-doc";
+import {
+ BasicExample,
+ WarningExample,
+ PlainExample,
+} from "#/examples/alert-dialog";
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/ginga-ui.com/src/app/components/alert/page.mdx b/app/ginga-ui.com/src/app/components/alert/page.mdx
new file mode 100644
index 0000000..8d8ebaa
--- /dev/null
+++ b/app/ginga-ui.com/src/app/components/alert/page.mdx
@@ -0,0 +1,18 @@
+import { ComponentDoc, Example } from "#/components/component-doc";
+import {
+ BasicExample,
+ VariantsExample,
+ DescriptionOnlyExample,
+} from "#/examples/alert";
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/ginga-ui.com/src/app/components/badge/page.mdx b/app/ginga-ui.com/src/app/components/badge/page.mdx
new file mode 100644
index 0000000..04131b5
--- /dev/null
+++ b/app/ginga-ui.com/src/app/components/badge/page.mdx
@@ -0,0 +1,18 @@
+import { ComponentDoc, Example } from "#/components/component-doc";
+import {
+ BasicExample,
+ VariantsExample,
+ WithTextExample,
+} from "#/examples/badge";
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/ginga-ui.com/src/app/components/drawer/page.mdx b/app/ginga-ui.com/src/app/components/drawer/page.mdx
new file mode 100644
index 0000000..d382e34
--- /dev/null
+++ b/app/ginga-ui.com/src/app/components/drawer/page.mdx
@@ -0,0 +1,18 @@
+import { ComponentDoc, Example } from "#/components/component-doc";
+import {
+ BasicExample,
+ PlacementsExample,
+ NotDismissableExample,
+} from "#/examples/drawer";
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/ginga-ui.com/src/app/components/page.tsx b/app/ginga-ui.com/src/app/components/page.tsx
index da6a52b..f89b831 100644
--- a/app/ginga-ui.com/src/app/components/page.tsx
+++ b/app/ginga-ui.com/src/app/components/page.tsx
@@ -25,7 +25,7 @@ export default function ComponentsPage() {
全コンポーネント
Ginga
- UIが提供する全19個のコンポーネントを確認できます。タブでカテゴリを切り替えて、各コンポーネントの詳細ページにアクセスできます。
+ UIが提供する全25個のコンポーネントを確認できます。タブでカテゴリを切り替えて、各コンポーネントの詳細ページにアクセスできます。
diff --git a/app/ginga-ui.com/src/app/components/popover/page.mdx b/app/ginga-ui.com/src/app/components/popover/page.mdx
new file mode 100644
index 0000000..6f0064f
--- /dev/null
+++ b/app/ginga-ui.com/src/app/components/popover/page.mdx
@@ -0,0 +1,18 @@
+import { ComponentDoc, Example } from "#/components/component-doc";
+import {
+ BasicExample,
+ PlacementsExample,
+ WithoutArrowExample,
+} from "#/examples/popover";
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/ginga-ui.com/src/app/components/toast/page.mdx b/app/ginga-ui.com/src/app/components/toast/page.mdx
new file mode 100644
index 0000000..fb13aae
--- /dev/null
+++ b/app/ginga-ui.com/src/app/components/toast/page.mdx
@@ -0,0 +1,18 @@
+import { ComponentDoc, Example } from "#/components/component-doc";
+import {
+ BasicExample,
+ VariantsExample,
+ TimeoutExample,
+} from "#/examples/toast";
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/ginga-ui.com/src/data/components.ts b/app/ginga-ui.com/src/data/components.ts
index f78dd9f..02153a1 100644
--- a/app/ginga-ui.com/src/data/components.ts
+++ b/app/ginga-ui.com/src/data/components.ts
@@ -85,6 +85,26 @@ const COMPONENTS: ComponentMetadata[] = [
category: "display",
description: "展開/折りたたみ可能なコンテンツセクション。",
},
+ {
+ id: "alert",
+ name: "Alert",
+ category: "display",
+ description:
+ "重要な情報をユーザーに伝えるアラート。4種類のバリアント(info、success、warning、danger)をサポート。",
+ },
+ {
+ id: "alert-dialog",
+ name: "AlertDialog",
+ category: "display",
+ description:
+ "ユーザーの確認を必須とするアラートダイアログ。破壊的な操作の確認などに使用。",
+ },
+ {
+ id: "badge",
+ name: "Badge",
+ category: "display",
+ description: "ステータスやラベルを表示する小さなバッジ。",
+ },
{
id: "card",
name: "Card",
@@ -98,6 +118,13 @@ const COMPONENTS: ComponentMetadata[] = [
description:
"モーダルダイアログ。ユーザーの注意を引く重要な情報や操作を表示。",
},
+ {
+ id: "drawer",
+ name: "Drawer",
+ category: "display",
+ description:
+ "画面の端からスライドインするドロワー。4方向(left、right、top、bottom)に対応。",
+ },
{
id: "image",
name: "Image",
@@ -110,6 +137,13 @@ const COMPONENTS: ComponentMetadata[] = [
category: "display",
description: "順序付きリストと順序なしリストを表示。",
},
+ {
+ id: "popover",
+ name: "Popover",
+ category: "display",
+ description:
+ "トリガー要素に紐づけて表示されるポップオーバー。配置方向と矢印表示をカスタマイズ可能。",
+ },
{
id: "table",
name: "Table",
@@ -123,6 +157,13 @@ const COMPONENTS: ComponentMetadata[] = [
description:
"タブで切り替え可能なコンテンツパネル。水平・垂直レイアウトに対応。",
},
+ {
+ id: "toast",
+ name: "Toast",
+ category: "display",
+ description:
+ "一時的な通知を表示するトースト。プログラマティックなAPI(toast.success等)で呼び出し可能。",
+ },
// Typography
{
diff --git a/app/ginga-ui.com/src/data/configs.ts b/app/ginga-ui.com/src/data/configs.ts
index a00f4a6..243c489 100644
--- a/app/ginga-ui.com/src/data/configs.ts
+++ b/app/ginga-ui.com/src/data/configs.ts
@@ -27,6 +27,43 @@ export const EXAMPLE_CONFIGS: Record
= {
description: "状態を外部から制御するアコーディオンです。",
},
],
+ alert: [
+ {
+ name: "BasicExample",
+ title: "基本的な使い方",
+ description: "タイトルと説明文を持つシンプルなアラートです。",
+ },
+ {
+ name: "VariantsExample",
+ title: "バリアント",
+ description:
+ "info、success、warning、dangerの4種類のバリアントを指定できます。",
+ },
+ {
+ name: "DescriptionOnlyExample",
+ title: "説明文のみ",
+ description:
+ "タイトルを省略して説明文だけを表示できます。動的に挿入して通知する場合はrole属性にalertを渡すか、Toastの利用を検討してください。",
+ },
+ ],
+ "alert-dialog": [
+ {
+ name: "BasicExample",
+ title: "基本的な使い方",
+ description:
+ "破壊的な操作の確認を求めるアラートダイアログです。外側をクリックしても閉じず、明示的な選択を求めます。",
+ },
+ {
+ name: "WarningExample",
+ title: "警告バリアント",
+ description: "variantプロップでタイトル色とアクセントを変更できます。",
+ },
+ {
+ name: "PlainExample",
+ title: "バリアントなし",
+ description: "variantを指定しない場合は通常の配色で表示されます。",
+ },
+ ],
anchor: [
{
name: "BasicExample",
@@ -49,6 +86,24 @@ export const EXAMPLE_CONFIGS: Record = {
description: "ボタンのようなスタイルでリンクを表示します。",
},
],
+ badge: [
+ {
+ name: "BasicExample",
+ title: "基本的な使い方",
+ description: "シンプルなバッジの使用例です。",
+ },
+ {
+ name: "VariantsExample",
+ title: "バリアント",
+ description:
+ "primary、secondary、info、success、warning、dangerの6種類のバリアントを指定できます。",
+ },
+ {
+ name: "WithTextExample",
+ title: "テキストと組み合わせる",
+ description: "テキストの横に件数などを表示できます。",
+ },
+ ],
box: [
{
name: "BasicExample",
@@ -160,6 +215,25 @@ export const EXAMPLE_CONFIGS: Record = {
description: "シンプルなアラートダイアログです。",
},
],
+ drawer: [
+ {
+ name: "BasicExample",
+ title: "基本的な使い方",
+ description: "画面の右端からスライドインするドロワーです。",
+ },
+ {
+ name: "PlacementsExample",
+ title: "表示位置",
+ description:
+ "placementプロップでleft、right、top、bottomの4方向を指定できます。",
+ },
+ {
+ name: "NotDismissableExample",
+ title: "外側クリックで閉じない",
+ description:
+ "dismissableプロップにfalseを渡すと、外側のクリックやESCキーでは閉じなくなります。",
+ },
+ ],
"form-control": [
{
name: "BasicExample",
@@ -293,6 +367,25 @@ export const EXAMPLE_CONFIGS: Record = {
description: "カスタムスタイルを適用した段落です。",
},
],
+ popover: [
+ {
+ name: "BasicExample",
+ title: "基本的な使い方",
+ description:
+ "トリガーの下に表示されるシンプルなポップオーバーです。フォーカス管理とキーボード操作に対応しています。",
+ },
+ {
+ name: "PlacementsExample",
+ title: "表示位置",
+ description:
+ "placementプロップでtop、bottom、left、rightなどの表示位置を指定できます。",
+ },
+ {
+ name: "WithoutArrowExample",
+ title: "矢印なし",
+ description: "showArrowプロップにfalseを渡すと矢印を非表示にできます。",
+ },
+ ],
radio: [
{
name: "BasicExample",
@@ -403,6 +496,26 @@ export const EXAMPLE_CONFIGS: Record = {
description: "状態を外部から制御するタブです。",
},
],
+ toast: [
+ {
+ name: "BasicExample",
+ title: "基本的な使い方",
+ description:
+ "ToastRegionをアプリのルートに1回だけ設置し、toast.showで通知を表示します。toast関数はクライアントのイベントハンドラから呼び出してください。",
+ },
+ {
+ name: "VariantsExample",
+ title: "バリアント",
+ description:
+ "toast.info、toast.success、toast.warning、toast.dangerで状態に応じた通知を表示できます。",
+ },
+ {
+ name: "TimeoutExample",
+ title: "自動クローズ",
+ description:
+ "timeoutオプションを指定すると自動的に閉じます。ホバーやフォーカス中はタイマーが一時停止します。",
+ },
+ ],
table: [
{
name: "BasicExample",
diff --git a/app/ginga-ui.com/src/examples/alert-dialog.tsx b/app/ginga-ui.com/src/examples/alert-dialog.tsx
new file mode 100644
index 0000000..ff3e362
--- /dev/null
+++ b/app/ginga-ui.com/src/examples/alert-dialog.tsx
@@ -0,0 +1,75 @@
+"use client";
+
+import {
+ AlertDialog,
+ AlertDialogTitle,
+ Button,
+ DialogTrigger,
+ Modal,
+} from "@ginga-ui/core";
+
+export function BasicExample() {
+ return (
+
+
+
+
+ ファイルの削除
+ この操作は取り消せません。本当に削除しますか?
+
+
+
+
+
+
+
+ );
+}
+
+export function WarningExample() {
+ return (
+
+
+
+
+ ログアウトの確認
+ 保存されていない変更は失われます。ログアウトしますか?
+
+
+
+
+
+
+
+ );
+}
+
+export function PlainExample() {
+ return (
+
+
+
+
+ 利用規約の更新
+ 利用規約が更新されました。続行するには同意が必要です。
+
+
+
+
+
+
+
+ );
+}
diff --git a/app/ginga-ui.com/src/examples/alert.tsx b/app/ginga-ui.com/src/examples/alert.tsx
new file mode 100644
index 0000000..08457a1
--- /dev/null
+++ b/app/ginga-ui.com/src/examples/alert.tsx
@@ -0,0 +1,47 @@
+"use client";
+
+import { Alert, AlertDescription, AlertTitle } from "@ginga-ui/core";
+
+export function BasicExample() {
+ return (
+
+ お知らせ
+
+ メンテナンスのため、明日の午前2時から4時までサービスを停止します。
+
+
+ );
+}
+
+export function VariantsExample() {
+ return (
+
+
+ 情報
+ 新しいバージョンが利用可能です。
+
+
+ 成功
+ 変更が保存されました。
+
+
+ 警告
+
+ ストレージの空き容量が少なくなっています。
+
+
+
+ エラー
+ データの読み込みに失敗しました。
+
+
+ );
+}
+
+export function DescriptionOnlyExample() {
+ return (
+
+ プロフィールを更新しました。
+
+ );
+}
diff --git a/app/ginga-ui.com/src/examples/badge.tsx b/app/ginga-ui.com/src/examples/badge.tsx
new file mode 100644
index 0000000..20b0388
--- /dev/null
+++ b/app/ginga-ui.com/src/examples/badge.tsx
@@ -0,0 +1,29 @@
+"use client";
+
+import { Badge } from "@ginga-ui/core";
+
+export function BasicExample() {
+ return New;
+}
+
+export function VariantsExample() {
+ return (
+
+ Primary
+ Secondary
+ Info
+ Success
+ Warning
+ Danger
+
+ );
+}
+
+export function WithTextExample() {
+ return (
+
+ 通知が届いています
+ 3
+
+ );
+}
diff --git a/app/ginga-ui.com/src/examples/drawer.tsx b/app/ginga-ui.com/src/examples/drawer.tsx
new file mode 100644
index 0000000..bfaf026
--- /dev/null
+++ b/app/ginga-ui.com/src/examples/drawer.tsx
@@ -0,0 +1,64 @@
+"use client";
+
+import {
+ Button,
+ DialogTrigger,
+ Drawer,
+ DrawerContent,
+ DrawerTitle,
+} from "@ginga-ui/core";
+
+export function BasicExample() {
+ return (
+
+
+
+
+ メニュー
+ 右側からスライドインするドロワーです。
+
+
+
+
+ );
+}
+
+export function PlacementsExample() {
+ return (
+
+ {(["left", "right", "top", "bottom"] as const).map((placement) => (
+
+
+
+
+ {placement}
+ {placement} から表示されるドロワーです。
+
+
+
+
+ ))}
+
+ );
+}
+
+export function NotDismissableExample() {
+ return (
+
+
+
+
+ 確認が必要です
+ 閉じるボタンを押すまでドロワーは閉じません。
+
+
+
+
+ );
+}
diff --git a/app/ginga-ui.com/src/examples/popover.tsx b/app/ginga-ui.com/src/examples/popover.tsx
new file mode 100644
index 0000000..4bb364c
--- /dev/null
+++ b/app/ginga-ui.com/src/examples/popover.tsx
@@ -0,0 +1,40 @@
+"use client";
+
+import { Button, Popover, PopoverTrigger } from "@ginga-ui/core";
+
+export function BasicExample() {
+ return (
+
+
+
+ これはポップオーバーの内容です。
+
+
+ );
+}
+
+export function PlacementsExample() {
+ return (
+
+ {(["top", "bottom", "left", "right"] as const).map((placement) => (
+
+
+
+ {placement} に表示されます。
+
+
+ ))}
+
+ );
+}
+
+export function WithoutArrowExample() {
+ return (
+
+
+
+ 矢印のないポップオーバーです。
+
+
+ );
+}
diff --git a/app/ginga-ui.com/src/examples/toast.tsx b/app/ginga-ui.com/src/examples/toast.tsx
new file mode 100644
index 0000000..7c10e7c
--- /dev/null
+++ b/app/ginga-ui.com/src/examples/toast.tsx
@@ -0,0 +1,63 @@
+"use client";
+
+import { Button, toast, ToastRegion } from "@ginga-ui/core";
+
+export function BasicExample() {
+ return (
+ <>
+
+
+ >
+ );
+}
+
+export function VariantsExample() {
+ return (
+
+
+
+
+
+
+ );
+}
+
+export function TimeoutExample() {
+ return (
+
+ );
+}
diff --git a/packages/core/src/components/alert-dialog/alert-dialog.stories.tsx b/packages/core/src/components/alert-dialog/alert-dialog.stories.tsx
new file mode 100644
index 0000000..8470050
--- /dev/null
+++ b/packages/core/src/components/alert-dialog/alert-dialog.stories.tsx
@@ -0,0 +1,66 @@
+import { Button } from "../button";
+import { DialogTrigger, Modal } from "../dialog";
+import { AlertDialog, AlertDialogTitle } from "./index";
+
+import type { Meta, StoryObj } from "@storybook/react";
+
+const meta: Meta = {
+ title: "Display/AlertDialog",
+ component: AlertDialog,
+ tags: ["autodocs"],
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Basic: Story = {
+ render: () => (
+
+
+
+
+ ファイルの削除
+ この操作は取り消せません。本当に削除しますか?
+
+
+
+
+
+
+
+ ),
+};
+
+export const Warning: Story = {
+ render: () => (
+
+
+
+
+ ログアウトの確認
+ 保存されていない変更は失われます。ログアウトしますか?
+
+
+
+
+
+
+
+ ),
+};
diff --git a/packages/core/src/components/alert-dialog/alert-dialog.tsx b/packages/core/src/components/alert-dialog/alert-dialog.tsx
new file mode 100644
index 0000000..465d988
--- /dev/null
+++ b/packages/core/src/components/alert-dialog/alert-dialog.tsx
@@ -0,0 +1,53 @@
+"use client";
+
+import { cn } from "@ginga-ui/utils";
+import {
+ Dialog as AriaDialog,
+ Heading as AriaHeading,
+} from "react-aria-components";
+import "./index.css";
+
+import type { OmitStrict } from "@ginga-ui/utils";
+
+export type AlertDialogProps = OmitStrict<
+ React.ComponentProps,
+ "role"
+> & {
+ variant?: "info" | "success" | "warning" | "danger";
+};
+
+export const AlertDialog: React.FC = ({
+ children,
+ className,
+ variant,
+ ...props
+}) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export type AlertDialogTitleProps = React.ComponentProps;
+
+export const AlertDialogTitle: React.FC = ({
+ children,
+ className,
+ ...props
+}) => {
+ return (
+
+ {children}
+
+ );
+};
diff --git a/packages/core/src/components/alert-dialog/index.css b/packages/core/src/components/alert-dialog/index.css
new file mode 100644
index 0000000..d0a7c83
--- /dev/null
+++ b/packages/core/src/components/alert-dialog/index.css
@@ -0,0 +1,53 @@
+.ginga-alert-dialog {
+ position: relative;
+ width: 100%;
+ max-width: 28rem;
+ padding: 1.5rem;
+ font-family: var(--font-family);
+ color: var(--color-secondary-9);
+ background-color: var(--color-background);
+ border-top: 0.25rem solid var(--color-secondary-9);
+ border-radius: calc(var(--size-radius) * 0.5);
+ box-shadow:
+ 0 0.25rem 0.375rem -0.0625rem rgb(0 0 0 / 10%),
+ 0 0.125rem 0.25rem -0.0625rem rgb(0 0 0 / 6%);
+
+ &[data-variant="info"] {
+ border-top-color: var(--color-info);
+
+ .ginga-alert-dialog-title {
+ color: var(--color-info-dark);
+ }
+ }
+
+ &[data-variant="success"] {
+ border-top-color: var(--color-success);
+
+ .ginga-alert-dialog-title {
+ color: var(--color-success-dark);
+ }
+ }
+
+ &[data-variant="warning"] {
+ border-top-color: var(--color-warning);
+
+ .ginga-alert-dialog-title {
+ color: var(--color-warning-dark);
+ }
+ }
+
+ &[data-variant="danger"] {
+ border-top-color: var(--color-danger);
+
+ .ginga-alert-dialog-title {
+ color: var(--color-danger-dark);
+ }
+ }
+}
+
+.ginga-alert-dialog-title {
+ margin-bottom: 1rem;
+ font-family: var(--font-family);
+ font-size: 1.25rem;
+ font-weight: 800;
+}
diff --git a/packages/core/src/components/alert-dialog/index.ts b/packages/core/src/components/alert-dialog/index.ts
new file mode 100644
index 0000000..28569ba
--- /dev/null
+++ b/packages/core/src/components/alert-dialog/index.ts
@@ -0,0 +1 @@
+export * from "./alert-dialog";
diff --git a/packages/core/src/components/alert/alert.stories.tsx b/packages/core/src/components/alert/alert.stories.tsx
new file mode 100644
index 0000000..c7d9392
--- /dev/null
+++ b/packages/core/src/components/alert/alert.stories.tsx
@@ -0,0 +1,56 @@
+import { Alert, AlertDescription, AlertTitle } from "./index";
+
+import type { Meta, StoryObj } from "@storybook/react";
+
+const meta: Meta = {
+ title: "Display/Alert",
+ component: Alert,
+ tags: ["autodocs"],
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Basic: Story = {
+ render: () => (
+
+ お知らせ
+
+ メンテナンスのため、明日の午前2時から4時までサービスを停止します。
+
+
+ ),
+};
+
+export const Variants: Story = {
+ render: () => (
+
+
+ 情報
+ 新しいバージョンが利用可能です。
+
+
+ 成功
+ 変更が保存されました。
+
+
+ 警告
+
+ ストレージの空き容量が少なくなっています。
+
+
+
+ エラー
+ データの読み込みに失敗しました。
+
+
+ ),
+};
+
+export const DescriptionOnly: Story = {
+ render: () => (
+
+ プロフィールを更新しました。
+
+ ),
+};
diff --git a/packages/core/src/components/alert/alert.tsx b/packages/core/src/components/alert/alert.tsx
new file mode 100644
index 0000000..d2ba2d3
--- /dev/null
+++ b/packages/core/src/components/alert/alert.tsx
@@ -0,0 +1,40 @@
+"use client";
+
+import { cn } from "@ginga-ui/utils";
+import "./index.css";
+
+export type AlertProps = React.HTMLAttributes & {
+ variant?: "info" | "success" | "warning" | "danger";
+};
+
+export const Alert: React.FC = ({
+ className,
+ variant = "info",
+ ...props
+}) => {
+ return (
+
+ );
+};
+
+export type AlertTitleProps = React.HTMLAttributes;
+
+export const AlertTitle: React.FC = ({
+ className,
+ ...props
+}) => {
+ return ;
+};
+
+export type AlertDescriptionProps = React.HTMLAttributes;
+
+export const AlertDescription: React.FC = ({
+ className,
+ ...props
+}) => {
+ return ;
+};
diff --git a/packages/core/src/components/alert/index.css b/packages/core/src/components/alert/index.css
new file mode 100644
index 0000000..e2d4ec1
--- /dev/null
+++ b/packages/core/src/components/alert/index.css
@@ -0,0 +1,43 @@
+.ginga-alert {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+ padding: 0.75rem 1rem;
+ font-family: var(--font-family);
+ border-left: 0.25rem solid;
+ border-radius: calc(var(--size-radius) * 0.5);
+
+ &[data-variant="info"] {
+ color: var(--color-info-dark);
+ background-color: var(--color-info-light);
+ border-left-color: var(--color-info);
+ }
+
+ &[data-variant="success"] {
+ color: var(--color-success-dark);
+ background-color: var(--color-success-light);
+ border-left-color: var(--color-success);
+ }
+
+ &[data-variant="warning"] {
+ color: var(--color-warning-dark);
+ background-color: var(--color-warning-light);
+ border-left-color: var(--color-warning);
+ }
+
+ &[data-variant="danger"] {
+ color: var(--color-danger-dark);
+ background-color: var(--color-danger-light);
+ border-left-color: var(--color-danger);
+ }
+}
+
+.ginga-alert-title {
+ font-size: 1rem;
+ font-weight: 700;
+}
+
+.ginga-alert-description {
+ margin: 0;
+ font-size: 0.875rem;
+}
diff --git a/packages/core/src/components/alert/index.ts b/packages/core/src/components/alert/index.ts
new file mode 100644
index 0000000..6e57847
--- /dev/null
+++ b/packages/core/src/components/alert/index.ts
@@ -0,0 +1 @@
+export * from "./alert";
diff --git a/packages/core/src/components/badge/badge.stories.tsx b/packages/core/src/components/badge/badge.stories.tsx
new file mode 100644
index 0000000..026df46
--- /dev/null
+++ b/packages/core/src/components/badge/badge.stories.tsx
@@ -0,0 +1,29 @@
+import { Badge } from "./index";
+
+import type { Meta, StoryObj } from "@storybook/react";
+
+const meta: Meta = {
+ title: "Display/Badge",
+ component: Badge,
+ tags: ["autodocs"],
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Basic: Story = {
+ render: () => New,
+};
+
+export const Variants: Story = {
+ render: () => (
+
+ Primary
+ Secondary
+ Info
+ Success
+ Warning
+ Danger
+
+ ),
+};
diff --git a/packages/core/src/components/badge/badge.tsx b/packages/core/src/components/badge/badge.tsx
new file mode 100644
index 0000000..b9512ed
--- /dev/null
+++ b/packages/core/src/components/badge/badge.tsx
@@ -0,0 +1,22 @@
+"use client";
+
+import { cn } from "@ginga-ui/utils";
+import "./index.css";
+
+export type BadgeProps = React.HTMLAttributes & {
+ variant?: "primary" | "secondary" | "info" | "success" | "warning" | "danger";
+};
+
+export const Badge: React.FC = ({
+ className,
+ variant = "primary",
+ ...props
+}) => {
+ return (
+
+ );
+};
diff --git a/packages/core/src/components/badge/index.css b/packages/core/src/components/badge/index.css
new file mode 100644
index 0000000..acafe21
--- /dev/null
+++ b/packages/core/src/components/badge/index.css
@@ -0,0 +1,42 @@
+.ginga-badge {
+ display: inline-flex;
+ gap: 0.25rem;
+ align-items: center;
+ padding: 0.125rem 0.5rem;
+ font-family: var(--font-family);
+ font-size: 0.75rem;
+ font-weight: 600;
+ line-height: 1.25rem;
+ white-space: nowrap;
+ border-radius: calc(var(--size-radius) * 0.5);
+
+ &[data-variant="primary"] {
+ color: var(--color-primary-9);
+ background-color: var(--color-primary-1);
+ }
+
+ &[data-variant="secondary"] {
+ color: var(--color-secondary-9);
+ background-color: var(--color-secondary-1);
+ }
+
+ &[data-variant="info"] {
+ color: var(--color-info-dark);
+ background-color: var(--color-info-light);
+ }
+
+ &[data-variant="success"] {
+ color: var(--color-success-dark);
+ background-color: var(--color-success-light);
+ }
+
+ &[data-variant="warning"] {
+ color: var(--color-warning-dark);
+ background-color: var(--color-warning-light);
+ }
+
+ &[data-variant="danger"] {
+ color: var(--color-danger-dark);
+ background-color: var(--color-danger-light);
+ }
+}
diff --git a/packages/core/src/components/badge/index.ts b/packages/core/src/components/badge/index.ts
new file mode 100644
index 0000000..80844a4
--- /dev/null
+++ b/packages/core/src/components/badge/index.ts
@@ -0,0 +1 @@
+export * from "./badge";
diff --git a/packages/core/src/components/drawer/drawer.stories.tsx b/packages/core/src/components/drawer/drawer.stories.tsx
new file mode 100644
index 0000000..d7957e4
--- /dev/null
+++ b/packages/core/src/components/drawer/drawer.stories.tsx
@@ -0,0 +1,63 @@
+import { Button } from "../button";
+import { DialogTrigger } from "../dialog";
+import { Drawer, DrawerContent, DrawerTitle } from "./index";
+
+import type { Meta, StoryObj } from "@storybook/react";
+
+const meta: Meta = {
+ title: "Display/Drawer",
+ component: Drawer,
+ tags: ["autodocs"],
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Basic: Story = {
+ render: () => (
+
+
+
+
+ メニュー
+ 右側からスライドインするドロワーです。
+
+
+
+
+ ),
+};
+
+export const Placements: Story = {
+ render: () => (
+
+ {(["left", "right", "top", "bottom"] as const).map((placement) => (
+
+
+
+
+ {placement}
+ {placement} から表示されるドロワーです。
+
+
+
+
+ ))}
+
+ ),
+};
+
+export const NotDismissable: Story = {
+ render: () => (
+
+
+
+
+ 確認が必要です
+ 閉じるボタンを押すまでドロワーは閉じません。
+
+
+
+
+ ),
+};
diff --git a/packages/core/src/components/drawer/drawer.tsx b/packages/core/src/components/drawer/drawer.tsx
new file mode 100644
index 0000000..a66844c
--- /dev/null
+++ b/packages/core/src/components/drawer/drawer.tsx
@@ -0,0 +1,71 @@
+"use client";
+
+import { cn } from "@ginga-ui/utils";
+import {
+ Dialog as AriaDialog,
+ Heading as AriaHeading,
+ Modal as AriaModal,
+ ModalOverlay as AriaModalOverlay,
+} from "react-aria-components";
+import "./index.css";
+
+import type { OmitStrict } from "@ginga-ui/utils";
+
+export type DrawerProps = OmitStrict<
+ React.ComponentProps,
+ "isDismissable"
+> & {
+ placement?: "left" | "right" | "top" | "bottom";
+ dismissable?: boolean;
+};
+
+export const Drawer: React.FC = ({
+ children,
+ className,
+ placement = "right",
+ dismissable = true,
+ ...props
+}) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export type DrawerContentProps = React.ComponentProps;
+
+export const DrawerContent: React.FC = ({
+ children,
+ className,
+ ...props
+}) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export type DrawerTitleProps = React.ComponentProps;
+
+export const DrawerTitle: React.FC = ({
+ children,
+ className,
+ ...props
+}) => {
+ return (
+
+ {children}
+
+ );
+};
diff --git a/packages/core/src/components/drawer/index.css b/packages/core/src/components/drawer/index.css
new file mode 100644
index 0000000..b5f39ec
--- /dev/null
+++ b/packages/core/src/components/drawer/index.css
@@ -0,0 +1,156 @@
+.ginga-drawer-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: 100;
+ width: 100%;
+ height: 100%;
+ background-color: rgb(0 0 0 / 50%);
+
+ &[data-entering] {
+ animation: ginga-drawer-fade 200ms ease-out;
+ }
+
+ &[data-exiting] {
+ animation: ginga-drawer-fade 200ms ease-in reverse;
+ }
+
+ .ginga-drawer {
+ position: absolute;
+ display: flex;
+ background-color: var(--color-background);
+ box-shadow:
+ 0 0.25rem 0.375rem -0.0625rem rgb(0 0 0 / 10%),
+ 0 0.125rem 0.25rem -0.0625rem rgb(0 0 0 / 6%);
+ }
+
+ &[data-placement="left"] .ginga-drawer {
+ top: 0;
+ left: 0;
+ width: 20rem;
+ max-width: 90vw;
+ height: 100%;
+
+ &[data-entering] {
+ animation: ginga-drawer-slide-left 200ms ease-out;
+ }
+
+ &[data-exiting] {
+ animation: ginga-drawer-slide-left 200ms ease-in reverse;
+ }
+ }
+
+ &[data-placement="right"] .ginga-drawer {
+ top: 0;
+ right: 0;
+ width: 20rem;
+ max-width: 90vw;
+ height: 100%;
+
+ &[data-entering] {
+ animation: ginga-drawer-slide-right 200ms ease-out;
+ }
+
+ &[data-exiting] {
+ animation: ginga-drawer-slide-right 200ms ease-in reverse;
+ }
+ }
+
+ &[data-placement="top"] .ginga-drawer {
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 16rem;
+ max-height: 90vh;
+
+ &[data-entering] {
+ animation: ginga-drawer-slide-top 200ms ease-out;
+ }
+
+ &[data-exiting] {
+ animation: ginga-drawer-slide-top 200ms ease-in reverse;
+ }
+ }
+
+ &[data-placement="bottom"] .ginga-drawer {
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 16rem;
+ max-height: 90vh;
+
+ &[data-entering] {
+ animation: ginga-drawer-slide-bottom 200ms ease-out;
+ }
+
+ &[data-exiting] {
+ animation: ginga-drawer-slide-bottom 200ms ease-in reverse;
+ }
+ }
+}
+
+.ginga-drawer-content {
+ flex: 1;
+ padding: 1.5rem;
+ overflow-y: auto;
+ font-family: var(--font-family);
+ color: var(--color-secondary-9);
+ outline: none;
+}
+
+.ginga-drawer-title {
+ margin-bottom: 1rem;
+ font-family: var(--font-family);
+ font-size: 1.5rem;
+ font-weight: 800;
+}
+
+@keyframes ginga-drawer-fade {
+ from {
+ opacity: 0;
+ }
+
+ to {
+ opacity: 1;
+ }
+}
+
+@keyframes ginga-drawer-slide-left {
+ from {
+ transform: translateX(-100%);
+ }
+
+ to {
+ transform: translateX(0);
+ }
+}
+
+@keyframes ginga-drawer-slide-right {
+ from {
+ transform: translateX(100%);
+ }
+
+ to {
+ transform: translateX(0);
+ }
+}
+
+@keyframes ginga-drawer-slide-top {
+ from {
+ transform: translateY(-100%);
+ }
+
+ to {
+ transform: translateY(0);
+ }
+}
+
+@keyframes ginga-drawer-slide-bottom {
+ from {
+ transform: translateY(100%);
+ }
+
+ to {
+ transform: translateY(0);
+ }
+}
diff --git a/packages/core/src/components/drawer/index.ts b/packages/core/src/components/drawer/index.ts
new file mode 100644
index 0000000..29c090d
--- /dev/null
+++ b/packages/core/src/components/drawer/index.ts
@@ -0,0 +1 @@
+export * from "./drawer";
diff --git a/packages/core/src/components/popover/index.css b/packages/core/src/components/popover/index.css
new file mode 100644
index 0000000..7895ed5
--- /dev/null
+++ b/packages/core/src/components/popover/index.css
@@ -0,0 +1,78 @@
+.ginga-popover {
+ z-index: 50;
+ min-width: 8rem;
+ background-color: var(--color-background);
+ border: var(--width-border) solid var(--color-secondary-9);
+ border-radius: calc(var(--size-radius) * 0.5);
+ box-shadow:
+ 0 0.25rem 0.375rem -0.0625rem rgb(0 0 0 / 10%),
+ 0 0.125rem 0.25rem -0.0625rem rgb(0 0 0 / 6%);
+
+ &[data-entering] {
+ animation: ginga-popover-enter 150ms ease-out;
+ }
+
+ &[data-exiting] {
+ animation: ginga-popover-exit 150ms ease-in;
+ }
+}
+
+.ginga-popover-arrow {
+ svg {
+ display: block;
+ fill: var(--color-background);
+ stroke: var(--color-secondary-9);
+ stroke-width: var(--width-border);
+ }
+
+ &[data-placement="top"] svg {
+ margin-top: calc(var(--width-border) * -1);
+ }
+
+ &[data-placement="bottom"] svg {
+ margin-bottom: calc(var(--width-border) * -1);
+ transform: rotate(180deg);
+ }
+
+ &[data-placement="left"] svg {
+ margin-left: calc(var(--width-border) * -1);
+ transform: rotate(-90deg);
+ }
+
+ &[data-placement="right"] svg {
+ margin-right: calc(var(--width-border) * -1);
+ transform: rotate(90deg);
+ }
+}
+
+.ginga-popover-dialog {
+ padding: 1rem;
+ font-family: var(--font-family);
+ font-size: 0.875rem;
+ color: var(--color-secondary-9);
+ outline: none;
+}
+
+@keyframes ginga-popover-enter {
+ from {
+ opacity: 0;
+ transform: scale(0.95);
+ }
+
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+@keyframes ginga-popover-exit {
+ from {
+ opacity: 1;
+ transform: scale(1);
+ }
+
+ to {
+ opacity: 0;
+ transform: scale(0.95);
+ }
+}
diff --git a/packages/core/src/components/popover/index.ts b/packages/core/src/components/popover/index.ts
new file mode 100644
index 0000000..79c7e29
--- /dev/null
+++ b/packages/core/src/components/popover/index.ts
@@ -0,0 +1 @@
+export * from "./popover";
diff --git a/packages/core/src/components/popover/popover.stories.tsx b/packages/core/src/components/popover/popover.stories.tsx
new file mode 100644
index 0000000..5aa96ed
--- /dev/null
+++ b/packages/core/src/components/popover/popover.stories.tsx
@@ -0,0 +1,57 @@
+import { Button } from "../button";
+import { Popover, PopoverTrigger } from "./index";
+
+import type { Meta, StoryObj } from "@storybook/react";
+
+const meta: Meta = {
+ title: "Display/Popover",
+ component: Popover,
+ tags: ["autodocs"],
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Basic: Story = {
+ render: () => (
+
+
+
+ これはポップオーバーの内容です。
+
+
+ ),
+};
+
+export const Placements: Story = {
+ render: () => (
+
+ {(["top", "bottom", "left", "right"] as const).map((placement) => (
+
+
+
+ {placement} に表示されます。
+
+
+ ))}
+
+ ),
+};
+
+export const WithoutArrow: Story = {
+ render: () => (
+
+
+
+ 矢印のないポップオーバーです。
+
+
+ ),
+};
diff --git a/packages/core/src/components/popover/popover.tsx b/packages/core/src/components/popover/popover.tsx
new file mode 100644
index 0000000..8ea5dd4
--- /dev/null
+++ b/packages/core/src/components/popover/popover.tsx
@@ -0,0 +1,56 @@
+"use client";
+
+import { cn } from "@ginga-ui/utils";
+import {
+ Dialog as AriaDialog,
+ DialogTrigger as AriaDialogTrigger,
+ OverlayArrow as AriaOverlayArrow,
+ Popover as AriaPopover,
+} from "react-aria-components";
+import "./index.css";
+
+import type { OmitStrict } from "@ginga-ui/utils";
+
+export type PopoverTriggerProps = React.ComponentProps<
+ typeof AriaDialogTrigger
+>;
+
+export const PopoverTrigger: React.FC = ({
+ children,
+ ...props
+}) => {
+ return {children};
+};
+
+export type PopoverProps = OmitStrict<
+ React.ComponentProps,
+ "children"
+> & {
+ children?: React.ReactNode;
+ showArrow?: boolean;
+};
+
+export const Popover: React.FC = ({
+ children,
+ className,
+ placement = "bottom",
+ showArrow = true,
+ ...props
+}) => {
+ return (
+
+ {showArrow && (
+
+
+
+ )}
+ {children}
+
+ );
+};
diff --git a/packages/core/src/components/toast/index.css b/packages/core/src/components/toast/index.css
new file mode 100644
index 0000000..510b5ee
--- /dev/null
+++ b/packages/core/src/components/toast/index.css
@@ -0,0 +1,127 @@
+.ginga-toast-region {
+ position: fixed;
+ z-index: 150;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ max-width: calc(100vw - 2rem);
+ outline: none;
+
+ &[data-placement="top-left"] {
+ top: 1rem;
+ left: 1rem;
+ }
+
+ &[data-placement="top-right"] {
+ top: 1rem;
+ right: 1rem;
+ }
+
+ &[data-placement="bottom-left"] {
+ bottom: 1rem;
+ left: 1rem;
+ }
+
+ &[data-placement="bottom-right"] {
+ right: 1rem;
+ bottom: 1rem;
+ }
+
+ &[data-focus-visible] {
+ outline: 0.125rem solid var(--color-primary-5);
+ outline-offset: 0.125rem;
+ }
+}
+
+.ginga-toast {
+ display: flex;
+ gap: 0.75rem;
+ align-items: flex-start;
+ width: 20rem;
+ max-width: 100%;
+ padding: 0.75rem 1rem;
+ font-family: var(--font-family);
+ border-left: 0.25rem solid;
+ border-radius: calc(var(--size-radius) * 0.5);
+ box-shadow:
+ 0 0.25rem 0.375rem -0.0625rem rgb(0 0 0 / 10%),
+ 0 0.125rem 0.25rem -0.0625rem rgb(0 0 0 / 6%);
+ animation: ginga-toast-enter 200ms ease-out;
+
+ &[data-variant="info"] {
+ color: var(--color-info-dark);
+ background-color: var(--color-info-light);
+ border-left-color: var(--color-info);
+ }
+
+ &[data-variant="success"] {
+ color: var(--color-success-dark);
+ background-color: var(--color-success-light);
+ border-left-color: var(--color-success);
+ }
+
+ &[data-variant="warning"] {
+ color: var(--color-warning-dark);
+ background-color: var(--color-warning-light);
+ border-left-color: var(--color-warning);
+ }
+
+ &[data-variant="danger"] {
+ color: var(--color-danger-dark);
+ background-color: var(--color-danger-light);
+ border-left-color: var(--color-danger);
+ }
+
+ &[data-focus-visible] {
+ outline: 0.125rem solid var(--color-primary-5);
+ outline-offset: 0.125rem;
+ }
+}
+
+.ginga-toast-content {
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ gap: 0.25rem;
+}
+
+.ginga-toast-title {
+ font-size: 0.875rem;
+ font-weight: 700;
+}
+
+.ginga-toast-description {
+ font-size: 0.8125rem;
+}
+
+.ginga-toast-close {
+ padding: 0 0.25rem;
+ font-size: 1rem;
+ line-height: 1.25rem;
+ color: inherit;
+ cursor: pointer;
+ background: none;
+ border: none;
+ border-radius: calc(var(--size-radius) * 0.25);
+
+ &[data-hovered] {
+ background-color: rgb(0 0 0 / 8%);
+ }
+
+ &[data-focus-visible] {
+ outline: 0.125rem solid var(--color-primary-5);
+ outline-offset: 0.0625rem;
+ }
+}
+
+@keyframes ginga-toast-enter {
+ from {
+ opacity: 0;
+ transform: translateY(0.5rem);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
diff --git a/packages/core/src/components/toast/index.ts b/packages/core/src/components/toast/index.ts
new file mode 100644
index 0000000..da08986
--- /dev/null
+++ b/packages/core/src/components/toast/index.ts
@@ -0,0 +1 @@
+export * from "./toast";
diff --git a/packages/core/src/components/toast/toast.stories.tsx b/packages/core/src/components/toast/toast.stories.tsx
new file mode 100644
index 0000000..85a0196
--- /dev/null
+++ b/packages/core/src/components/toast/toast.stories.tsx
@@ -0,0 +1,68 @@
+import { Button } from "../button";
+import { toast, ToastRegion } from "./index";
+
+import type { Meta, StoryObj } from "@storybook/react";
+
+const meta: Meta = {
+ title: "Display/Toast",
+ component: ToastRegion,
+ tags: ["autodocs"],
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Basic: Story = {
+ render: () => (
+ <>
+
+
+ >
+ ),
+};
+
+export const Variants: Story = {
+ render: () => (
+ <>
+
+
+
+
+
+
+
+ >
+ ),
+};
+
+export const WithDescriptionAndTimeout: Story = {
+ render: () => (
+ <>
+
+
+ >
+ ),
+};
diff --git a/packages/core/src/components/toast/toast.tsx b/packages/core/src/components/toast/toast.tsx
new file mode 100644
index 0000000..e96aed0
--- /dev/null
+++ b/packages/core/src/components/toast/toast.tsx
@@ -0,0 +1,110 @@
+"use client";
+
+import { cn } from "@ginga-ui/utils";
+import {
+ Button as AriaButton,
+ Text as AriaText,
+ UNSTABLE_Toast as AriaToast,
+ UNSTABLE_ToastContent as AriaToastContent,
+ UNSTABLE_ToastQueue as AriaToastQueue,
+ UNSTABLE_ToastRegion as AriaToastRegion,
+} from "react-aria-components";
+import "./index.css";
+
+import type { OmitStrict } from "@ginga-ui/utils";
+import type { ToastRegionProps as AriaToastRegionProps } from "react-aria-components";
+
+export type ToastVariant = "info" | "success" | "warning" | "danger";
+
+export type ToastContentValue = {
+ title: string;
+ description?: string;
+ variant?: ToastVariant;
+};
+
+export type ToastOptions = {
+ description?: string;
+ timeout?: number;
+ onClose?: () => void;
+};
+
+const defaultQueue = new AriaToastQueue({
+ maxVisibleToasts: 5,
+});
+
+const show = (
+ title: string,
+ options: ToastOptions & { variant?: ToastVariant } = {}
+): string => {
+ const { description, variant, timeout, onClose } = options;
+ return defaultQueue.add(
+ { title, description, variant },
+ {
+ timeout,
+ onClose,
+ }
+ );
+};
+
+export const toast = {
+ show,
+ info: (title: string, options: ToastOptions = {}): string =>
+ show(title, { ...options, variant: "info" }),
+ success: (title: string, options: ToastOptions = {}): string =>
+ show(title, { ...options, variant: "success" }),
+ warning: (title: string, options: ToastOptions = {}): string =>
+ show(title, { ...options, variant: "warning" }),
+ danger: (title: string, options: ToastOptions = {}): string =>
+ show(title, { ...options, variant: "danger" }),
+ close: (key: string): void => defaultQueue.close(key),
+};
+
+export type ToastRegionProps = OmitStrict<
+ AriaToastRegionProps,
+ "queue" | "children"
+> & {
+ queue?: AriaToastQueue;
+ placement?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
+};
+
+export const ToastRegion: React.FC = ({
+ className,
+ queue = defaultQueue,
+ placement = "bottom-right",
+ ...props
+}) => {
+ return (
+
+ {({ toast: queuedToast }) => (
+
+
+
+ {queuedToast.content.title}
+
+ {queuedToast.content.description && (
+
+ {queuedToast.content.description}
+
+ )}
+
+
+ ×
+
+
+ )}
+
+ );
+};
diff --git a/packages/core/src/index.css b/packages/core/src/index.css
index b169971..b40fd2d 100644
--- a/packages/core/src/index.css
+++ b/packages/core/src/index.css
@@ -1,20 +1,26 @@
@import url("./components/accordion/index.css");
+@import url("./components/alert/index.css");
+@import url("./components/alert-dialog/index.css");
@import url("./components/anchor/index.css");
+@import url("./components/badge/index.css");
@import url("./components/box/index.css");
@import url("./components/button/index.css");
@import url("./components/card/index.css");
@import url("./components/checkbox/index.css");
@import url("./components/code-block/index.css");
@import url("./components/dialog/index.css");
+@import url("./components/drawer/index.css");
@import url("./components/form-control/index.css");
@import url("./components/heading/index.css");
@import url("./components/image/index.css");
@import url("./components/input/index.css");
@import url("./components/list/index.css");
@import url("./components/paragraph/index.css");
+@import url("./components/popover/index.css");
@import url("./components/radio/index.css");
@import url("./components/select/index.css");
@import url("./components/slider/index.css");
@import url("./components/switch/index.css");
@import url("./components/tab/index.css");
@import url("./components/table/index.css");
+@import url("./components/toast/index.css");
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 75f3fc8..ba3eb27 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -1,20 +1,26 @@
export * from "./components/accordion";
+export * from "./components/alert";
+export * from "./components/alert-dialog";
export * from "./components/anchor";
+export * from "./components/badge";
export * from "./components/box";
export * from "./components/button";
export * from "./components/card";
export * from "./components/checkbox";
export * from "./components/code-block";
export * from "./components/dialog";
+export * from "./components/drawer";
export * from "./components/form-control";
export * from "./components/heading";
export * from "./components/image";
export * from "./components/input";
export * from "./components/list";
export * from "./components/paragraph";
+export * from "./components/popover";
export * from "./components/radio";
export * from "./components/select";
export * from "./components/slider";
export * from "./components/switch";
export * from "./components/tab";
export * from "./components/table";
+export * from "./components/toast";
diff --git a/packages/core/src/variables.css b/packages/core/src/variables.css
index e9a600d..b9144e2 100644
--- a/packages/core/src/variables.css
+++ b/packages/core/src/variables.css
@@ -24,6 +24,18 @@
--color-white: #fff;
--color-black: #000;
--color-background: #fff;
+ --color-info: #1677ff;
+ --color-info-light: #e6f4ff;
+ --color-info-dark: #003eb3;
+ --color-success: #52c41a;
+ --color-success-light: #f6ffed;
+ --color-success-dark: #237804;
+ --color-warning: #faad14;
+ --color-warning-light: #fffbe6;
+ --color-warning-dark: #ad6800;
+ --color-danger: #ff4d4f;
+ --color-danger-light: #fff1f0;
+ --color-danger-dark: #a8071a;
--size-radius: 1rem;
--width-border: 2px;
--font-family: sans-serif;