Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions .changeset/plenty-moons-shake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@ginga-ui/core": minor
---

CodeBlock コンポーネントを追加し、Card と Anchor のスタイルを見直し

- CodeBlock コンポーネントを新規追加。クリップボードコピー機能を内蔵し、シンタックスハイライト済みの React 要素を children で注入可能(shiki 等は非内包)
- Card の視覚変更: border を `--color-primary-9` に変更し、box-shadow を削除
- Anchor `variant="button"` の視覚変更: Button と統一(角丸を `calc(var(--size-radius) * 0.5)` に、フォーカスリングを outline 方式に、disabled 表現と transition を Button と同一に)
35 changes: 1 addition & 34 deletions app/ginga-ui.com/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -142,41 +142,8 @@ body {
}

/* Code Block */
.code-block {
.ginga-code-block {
margin: 1.5rem 0;
overflow: hidden;
border: 1px solid var(--color-primary-2);
border-radius: var(--size-radius);
}

.code-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1rem;
background-color: var(--color-primary-1);
border-bottom: 1px solid var(--color-primary-2);
}

.code-filename {
font-size: 0.875rem;
font-weight: 500;
color: var(--color-secondary);
}

.copy-button {
font-size: 0.875rem;
}

.code-content {
overflow-x: auto;
}

.code-content pre {
padding: 1rem;
margin: 0;
font-size: 0.875rem;
line-height: 1.6;
}

/* Component Grid */
Expand Down
5 changes: 0 additions & 5 deletions app/ginga-ui.com/src/app/page.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,3 @@
.gettingStartedContent {
margin-top: 1.5rem;
}

.primaryLink {
font-weight: 600;
color: var(--color-primary);
}
6 changes: 2 additions & 4 deletions app/ginga-ui.com/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Link from "next/link";
import { Heading, Paragraph, Button, Card } from "@ginga-ui/core";
import { Anchor, Heading, Paragraph, Button, Card } from "@ginga-ui/core";
import styles from "./page.module.css";

export default function Home() {
Expand Down Expand Up @@ -82,9 +82,7 @@ export default function Home() {
</Paragraph>
<Paragraph>
詳しい手順は
<Link href="/quick-start" className={styles.primaryLink}>
クイックスタート
</Link>
<Anchor href="/quick-start">クイックスタート</Anchor>
をご覧ください。
</Paragraph>
</div>
Expand Down
5 changes: 0 additions & 5 deletions app/ginga-ui.com/src/app/quick-start/page.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,3 @@
.nextStepsList {
line-height: 2;
}

.primaryLink {
font-weight: 600;
color: var(--color-primary);
}
10 changes: 3 additions & 7 deletions app/ginga-ui.com/src/app/quick-start/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Heading, Paragraph } from "@ginga-ui/core";
import { Anchor, Heading, Paragraph } from "@ginga-ui/core";
import { CodeBlock } from "#/components/code-block";
import { highlightCode } from "#/lib/shiki";
import styles from "./page.module.css";
Expand Down Expand Up @@ -190,15 +190,11 @@ export default function Home() {
<Heading level="h2">次のステップ</Heading>
<ul className={styles.nextStepsList}>
<li>
<a href="/components" className={styles.primaryLink}>
全コンポーネント
</a>
<Anchor href="/components">全コンポーネント</Anchor>
を確認して、利用可能なUIパーツを探索
</li>
<li>
<a href="/theme-generation" className={styles.primaryLink}>
テーマ生成
</a>
<Anchor href="/theme-generation">テーマ生成</Anchor>
を試して、AIによる自動スタイリングを体験
</li>
<li>
Expand Down
47 changes: 12 additions & 35 deletions app/ginga-ui.com/src/components/code-block.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,22 @@
"use client";

import { useState } from "react";
import { Button } from "@ginga-ui/core";
import { CodeBlock as CoreCodeBlock } from "@ginga-ui/core";

type CodeBlockProps = {
code: string;
highlightedCode: string;
highlightedCode?: string;
filename?: string;
};

export function CodeBlock({ code, highlightedCode, filename }: CodeBlockProps) {
const [copied, setCopied] = useState(false);

const handleCopy = async () => {
try {
if (!navigator?.clipboard?.writeText) {
throw new Error("Clipboard API is not available");
}
await navigator.clipboard.writeText(code);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (error) {
console.error("Failed to copy code to clipboard:", error);
window.alert(
"クリップボードへのコピーに失敗しました。ブラウザの設定やHTTPS環境をご確認ください。"
);
}
};

return (
<div className="code-block">
<div className="code-header">
{filename && <span className="code-filename">{filename}</span>}
<Button variant="light" onPress={handleCopy} className="copy-button">
{copied ? "コピー完了!" : "コピー"}
</Button>
</div>
<div
className="code-content"
dangerouslySetInnerHTML={{ __html: highlightedCode }}
/>
</div>
<CoreCodeBlock
code={code}
filename={filename}
copyLabel="コピー"
copiedLabel="コピー完了!"
>
{highlightedCode ? (
<div dangerouslySetInnerHTML={{ __html: highlightedCode }} />
) : null}
</CoreCodeBlock>
);
}
2 changes: 1 addition & 1 deletion app/ginga-ui.com/src/components/theme-generator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export function ThemeGenerator() {
<p className={styles.resultDescription}>
テーマがサイト全体に適用されました。ページをスクロールして変化を確認してください。
</p>
<CodeBlock code={cssCode} highlightedCode={`<pre>${cssCode}</pre>`} />
<CodeBlock code={cssCode} />
</div>
)}
</div>
Expand Down
23 changes: 19 additions & 4 deletions packages/core/src/components/anchor/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,19 @@
}

&[data-variant="button"] {
display: inline-block;
display: inline-flex;
gap: 0.5rem;
align-items: center;
padding: 0.5rem 1rem;
margin: 0;
font-size: 1rem;
vertical-align: middle;
color: var(--color-background);
text-align: center;
text-decoration: none;
background-color: var(--color-primary-9);
border-radius: var(--size-radius);
border-radius: calc(var(--size-radius) * 0.5);
transition: all 0.2s;

&[data-hovered] {
background-color: var(--color-primary-8);
Expand All @@ -44,11 +51,19 @@
}

&[data-disabled] {
background-color: var(--color-secondary-5);
color: var(--color-secondary-6);
pointer-events: none;
cursor: not-allowed;
background-color: var(--color-secondary-1);
}

&[data-focus-visible] {
outline: 2px solid var(--color-primary-5);
outline-offset: -1px;
}

&[data-focus-visible]::after {
border-color: var(--color-background);
content: none;
}
}
}
3 changes: 1 addition & 2 deletions packages/core/src/components/card/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
font-family: var(--font-family);
color: var(--color-secondary-9);
background-color: var(--color-background);
border: 1px solid var(--color-secondary-9);
border: 1px solid var(--color-primary-9);
border-radius: calc(var(--size-radius) * 0.5);
box-shadow: 0 1px 2px rgb(0 0 0 / 30%);
}

.ginga-card-header {
Expand Down
54 changes: 54 additions & 0 deletions packages/core/src/components/code-block/code-block.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { CodeBlock } from "./code-block";

import type { Meta, StoryObj } from "@storybook/react";

const sampleCode = `import { Button } from "@ginga-ui/core";

export const App = () => <Button>Click me</Button>;`;

const meta: Meta<typeof CodeBlock> = {
title: "Display/CodeBlock",
component: CodeBlock,
tags: ["autodocs"],
};

export default meta;
type Story = StoryObj<typeof CodeBlock>;

export const Default: Story = {
args: { code: sampleCode },
};

export const WithFilename: Story = {
args: { code: sampleCode, filename: "app.tsx" },
};

export const WithHighlightedChildren: Story = {
args: {
code: sampleCode,
filename: "app.tsx",
children: (
<pre>
<code>
<span style={{ color: "#c678dd" }}>import</span>
<span> {"{ Button }"} </span>
<span style={{ color: "#c678dd" }}>from</span>
<span style={{ color: "#98c379" }}> "@ginga-ui/core"</span>
<span>;</span>
{"\n\n"}
<span style={{ color: "#c678dd" }}>export const</span>
<span style={{ color: "#61afef" }}> App</span>
<span> = () =&gt; </span>
<span style={{ color: "#e06c75" }}>{"<Button>"}</span>
<span>Click me</span>
<span style={{ color: "#e06c75" }}>{"</Button>"}</span>
<span>;</span>
</code>
</pre>
),
},
};

export const CustomLabels: Story = {
args: { code: sampleCode, copyLabel: "コピー", copiedLabel: "コピー完了!" },
};
58 changes: 58 additions & 0 deletions packages/core/src/components/code-block/code-block.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"use client";

import { useState } from "react";

import { cn } from "@ginga-ui/utils";

import { Button } from "../button";
import "./index.css";

export type CodeBlockProps = React.HTMLAttributes<HTMLDivElement> & {
code: string;
filename?: string;
copyLabel?: string;
copiedLabel?: string;
};

export const CodeBlock: React.FC<CodeBlockProps> = ({
code,
filename,
copyLabel = "Copy",
copiedLabel = "Copied!",
className,
children,
...props
}) => {
const [copied, setCopied] = useState(false);

const handleCopy = async () => {
if (!navigator?.clipboard?.writeText) return;
await navigator.clipboard.writeText(code);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};

return (
<div className={cn("ginga-code-block", className)} {...props}>
<div className="ginga-code-block-header">
{filename && (
<span className="ginga-code-block-filename">{filename}</span>
)}
<Button
variant="light"
className="ginga-code-block-copy-button"
onPress={handleCopy}
>
<span aria-live="polite">{copied ? copiedLabel : copyLabel}</span>
</Button>
</div>
<div className="ginga-code-block-content">
{children ?? (
<pre>
<code>{code}</code>
</pre>
)}
</div>
</div>
);
};
39 changes: 39 additions & 0 deletions packages/core/src/components/code-block/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
.ginga-code-block {
overflow: hidden;
font-family: var(--font-family);
color: var(--color-secondary-9);
background-color: var(--color-background);
border: 1px solid var(--color-primary-2);
border-radius: calc(var(--size-radius) * 0.5);
}

.ginga-code-block-header {
display: flex;
gap: 0.5rem;
align-items: center;
padding: 0.75rem 1rem;
background-color: var(--color-primary-1);
border-bottom: 1px solid var(--color-primary-2);
}

.ginga-code-block-filename {
font-size: 0.875rem;
font-weight: 500;
color: var(--color-secondary);
}

.ginga-code-block-copy-button {
margin-left: auto;
font-size: 0.875rem;
}

.ginga-code-block-content {
overflow-x: auto;

& pre {
padding: 1rem;
margin: 0;
font-size: 0.875rem;
line-height: 1.6;
}
}
1 change: 1 addition & 0 deletions packages/core/src/components/code-block/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./code-block";
1 change: 1 addition & 0 deletions packages/core/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
@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/form-control/index.css");
@import url("./components/heading/index.css");
Expand Down
Loading