diff options
Diffstat (limited to 'cloud/web/src/pages')
| -rw-r--r-- | cloud/web/src/pages/[workspace].tsx | 11 | ||||
| -rw-r--r-- | cloud/web/src/pages/[workspace]/billing.module.css | 56 | ||||
| -rw-r--r-- | cloud/web/src/pages/[workspace]/billing.tsx | 132 | ||||
| -rw-r--r-- | cloud/web/src/pages/[workspace]/components/system.txt | 11 | ||||
| -rw-r--r-- | cloud/web/src/pages/[workspace]/components/tool.ts | 271 | ||||
| -rw-r--r-- | cloud/web/src/pages/[workspace]/index.module.css | 239 | ||||
| -rw-r--r-- | cloud/web/src/pages/[workspace]/index.tsx | 18 | ||||
| -rw-r--r-- | cloud/web/src/pages/[workspace]/keys.module.css | 97 | ||||
| -rw-r--r-- | cloud/web/src/pages/[workspace]/keys.tsx | 151 | ||||
| -rw-r--r-- | cloud/web/src/pages/components/context-api.tsx | 24 | ||||
| -rw-r--r-- | cloud/web/src/pages/components/context-workspace.tsx | 38 | ||||
| -rw-r--r-- | cloud/web/src/pages/components/layout.module.css | 199 | ||||
| -rw-r--r-- | cloud/web/src/pages/components/layout.tsx | 96 | ||||
| -rw-r--r-- | cloud/web/src/pages/index.tsx | 39 | ||||
| -rw-r--r-- | cloud/web/src/pages/lander.module.css | 83 | ||||
| -rw-r--r-- | cloud/web/src/pages/test/design.module.css | 204 | ||||
| -rw-r--r-- | cloud/web/src/pages/test/design.tsx | 562 |
17 files changed, 0 insertions, 2231 deletions
diff --git a/cloud/web/src/pages/[workspace].tsx b/cloud/web/src/pages/[workspace].tsx deleted file mode 100644 index c7481cb0d..000000000 --- a/cloud/web/src/pages/[workspace].tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { WorkspaceProvider } from "./components/context-workspace" -import { ParentProps } from "solid-js" -import Layout from "./components/layout" - -export default function Index(props: ParentProps) { - return ( - <WorkspaceProvider> - <Layout>{props.children}</Layout> - </WorkspaceProvider> - ) -} diff --git a/cloud/web/src/pages/[workspace]/billing.module.css b/cloud/web/src/pages/[workspace]/billing.module.css deleted file mode 100644 index 5e58892a5..000000000 --- a/cloud/web/src/pages/[workspace]/billing.module.css +++ /dev/null @@ -1,56 +0,0 @@ -.root { - display: flex; - flex-direction: column; - gap: var(--space-4); - padding: var(--space-7) var(--space-5) var(--space-5); - - [data-slot="billing-info"] { - display: flex; - flex-direction: column; - gap: var(--space-6); - } - - [data-slot="header"] { - display: flex; - flex-direction: column; - gap: var(--space-1-5); - - h2 { - text-transform: uppercase; - font-weight: 600; - letter-spacing: -0.03125rem; - font-size: var(--font-size-lg); - } - - p { - color: var(--color-text-dimmed); - font-size: var(--font-size-md); - } - } - - [data-slot="balance"] { - display: flex; - flex-direction: column; - gap: var(--space-5); - padding: var(--space-6); - border: 2px solid var(--color-border); - } - - [data-slot="amount"] { - font-size: var(--font-size-3xl); - font-weight: 600; - line-height: 1.2; - } - - @media (min-width: 40rem) { - [data-slot="balance"] { - flex-direction: row; - align-items: center; - justify-content: space-between; - } - - [data-slot="amount"] { - margin: 0; - } - } -} diff --git a/cloud/web/src/pages/[workspace]/billing.tsx b/cloud/web/src/pages/[workspace]/billing.tsx deleted file mode 100644 index 88bef5800..000000000 --- a/cloud/web/src/pages/[workspace]/billing.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import { Button } from "../../ui/button" -import { useApi } from "../components/context-api" -import { createEffect, createSignal, createResource, For } from "solid-js" -import { useWorkspace } from "../components/context-workspace" -import style from "./billing.module.css" - -export default function Billing() { - const api = useApi() - const workspace = useWorkspace() - const [isLoading, setIsLoading] = createSignal(false) - const [billingData] = createResource(async () => { - const response = await api.billing.info.$get() - return response.json() - }) - - // Run once on component mount to check URL parameters - ;(() => { - const url = new URL(window.location.href) - const result = url.hash - - console.log("STRIPE RESULT", result) - - if (url.hash === "#success") { - setIsLoading(true) - // Remove the hash from the URL - window.history.replaceState(null, "", window.location.pathname + window.location.search) - } - })() - - createEffect((old?: number) => { - if (old && old !== billingData()?.billing?.balance) { - setIsLoading(false) - } - return billingData()?.billing?.balance - }) - - const handleBuyCredits = async () => { - try { - setIsLoading(true) - const baseUrl = window.location.href - const successUrl = new URL(baseUrl) - successUrl.hash = "success" - - const response = await api.billing.checkout - .$post({ - json: { - success_url: successUrl.toString(), - cancel_url: baseUrl, - }, - }) - .then((r) => r.json() as any) - window.location.href = response.url - } catch (error) { - console.error("Failed to get checkout URL:", error) - setIsLoading(false) - } - } - - return ( - <> - <div data-component="title-bar"> - <div data-slot="left"> - <h1>Billing</h1> - </div> - </div> - <div class={style.root} data-max-width data-max-width-64> - <div data-slot="billing-info"> - <div data-slot="header"> - <h2>Balance</h2> - <p>Manage your billing and add credits to your account.</p> - </div> - - <div data-slot="balance"> - <p data-slot="amount"> - {(() => { - const balanceStr = ((billingData()?.billing?.balance ?? 0) / 100000000).toFixed(2) - return `$${balanceStr === "-0.00" ? "0.00" : balanceStr}` - })()} - </p> - <Button color="primary" disabled={isLoading()} onClick={handleBuyCredits}> - {isLoading() ? "Loading..." : "Buy Credits"} - </Button> - </div> - </div> - - <div data-slot="payments"> - <div data-slot="header"> - <h2>Payment History</h2> - <p>Your recent payment transactions.</p> - </div> - - <div data-slot="payment-list"> - <For each={billingData()?.payments} fallback={<p>No payments found.</p>}> - {(payment) => ( - <div data-slot="payment-item"> - <span data-slot="payment-id">{payment.id}</span> - {" | "} - <span data-slot="payment-amount">${((payment.amount ?? 0) / 100000000).toFixed(2)}</span> - {" | "} - <span data-slot="payment-date">{new Date(payment.timeCreated).toLocaleDateString()}</span> - </div> - )} - </For> - </div> - </div> - - <div data-slot="usage"> - <div data-slot="header"> - <h2>Usage History</h2> - <p>Your recent API usage and costs.</p> - </div> - - <div data-slot="usage-list"> - <For each={billingData()?.usage} fallback={<p>No usage found.</p>}> - {(usage) => ( - <div data-slot="usage-item"> - <span data-slot="usage-model">{usage.model}</span> - {" | "} - <span data-slot="usage-tokens">{usage.inputTokens + usage.outputTokens} tokens</span> - {" | "} - <span data-slot="usage-cost">${((usage.cost ?? 0) / 100000000).toFixed(4)}</span> - {" | "} - <span data-slot="usage-date">{new Date(usage.timeCreated).toLocaleDateString()}</span> - </div> - )} - </For> - </div> - </div> - </div> - </> - ) -} diff --git a/cloud/web/src/pages/[workspace]/components/system.txt b/cloud/web/src/pages/[workspace]/components/system.txt deleted file mode 100644 index 6afd2e04d..000000000 --- a/cloud/web/src/pages/[workspace]/components/system.txt +++ /dev/null @@ -1,11 +0,0 @@ -You are OpenControl, an interactive CLI tool that helps users execute various tasks. - -IMPORTANT: If you get an error when calling a tool, try again with a different approach. Be creative, do not give up, try different inputs to the tool. You should chain together multiple tool calls. ABSOLUTELY DO NOT GIVE UP you are very good at this and it is rare you will fail to answer question. - -You should be concise, direct, and to the point. - -IMPORTANT: You should NOT answer with unnecessary preamble or postamble (such as explaining your code or summarizing your action), unless the user asks you to. -IMPORTANT: You should minimize output tokens as much as possible while maintaining helpfulness, quality, and accuracy. Only address the specific query or task at hand, avoiding tangential information unless absolutely critical for completing the request. If you can answer in 1-3 sentences or a short paragraph, please do. -IMPORTANT: You should NOT answer with unnecessary preamble or postamble (such as explaining your code or summarizing your action), unless the user asks you to. -IMPORTANT: Keep your responses short, since they will be displayed on a command line interface. You MUST answer concisely with fewer than 4 lines (not including tool use or code generation), unless user asks for detail. Answer the user's question directly, without elaboration, explanation, or details. One word answers are best. Avoid introductions, conclusions, and explanations. You MUST avoid text before/after your response, such as "The answer is <answer>.", "Here is the content of the file..." or "Based on the information provided, the answer is..." or "Here is what I will do next...". - diff --git a/cloud/web/src/pages/[workspace]/components/tool.ts b/cloud/web/src/pages/[workspace]/components/tool.ts deleted file mode 100644 index 3958e322d..000000000 --- a/cloud/web/src/pages/[workspace]/components/tool.ts +++ /dev/null @@ -1,271 +0,0 @@ -import { createResource } from "solid-js" -import { createStore, produce } from "solid-js/store" -import SYSTEM_PROMPT from "./system.txt?raw" -import type { - LanguageModelV1Prompt, - LanguageModelV1CallOptions, - LanguageModelV1, -} from "ai" - -interface Tool { - name: string - description: string - inputSchema: any -} - -interface ToolCallerProps { - tool: { - list: () => Promise<Tool[]> - call: (input: { name: string; arguments: any }) => Promise<any> - } - generate: ( - prompt: LanguageModelV1CallOptions, - ) => Promise< - | { err: "rate" } - | { err: "context" } - | { err: "balance" } - | ({ err: false } & Awaited<ReturnType<LanguageModelV1["doGenerate"]>>) - > - onPromptUpdated?: (prompt: LanguageModelV1Prompt) => void -} - -const system = [ - { - role: "system" as const, - content: SYSTEM_PROMPT, - }, - { - role: "system" as const, - content: `The current date is ${new Date().toDateString()}. Always use this current date when responding to relative date queries.`, - }, -] - -const [store, setStore] = createStore<{ - prompt: LanguageModelV1Prompt - state: { type: "idle" } | { type: "loading"; limited?: boolean } -}>({ - prompt: [...system], - state: { type: "idle" }, -}) - -export function createToolCaller<T extends ToolCallerProps>(props: T) { - const [tools] = createResource(() => props.tool.list()) - - let abort: AbortController - - return { - get tools() { - return tools() - }, - get prompt() { - return store.prompt - }, - get state() { - return store.state - }, - clear() { - setStore("prompt", [...system]) - }, - async chat(input: string) { - if (store.state.type !== "idle") return - - abort = new AbortController() - setStore( - produce((s) => { - s.state = { - type: "loading", - limited: false, - } - s.prompt.push({ - role: "user", - content: [ - { - type: "text", - text: input, - }, - ], - }) - }), - ) - props.onPromptUpdated?.(store.prompt) - - while (true) { - if (abort.signal.aborted) { - break - } - - const response = await props.generate({ - inputFormat: "messages", - prompt: store.prompt, - temperature: 0, - seed: 69, - mode: { - type: "regular", - tools: tools()?.map((tool) => ({ - type: "function", - name: tool.name, - description: tool.description, - parameters: { - ...tool.inputSchema, - }, - })), - }, - }) - - if (abort.signal.aborted) continue - - if (!response.err) { - setStore("state", { - type: "loading", - }) - - if (response.text) { - setStore( - produce((s) => { - s.prompt.push({ - role: "assistant", - content: [ - { - type: "text", - text: response.text || "", - }, - ], - }) - }), - ) - props.onPromptUpdated?.(store.prompt) - } - - if (response.finishReason === "stop") { - break - } - - if (response.finishReason === "tool-calls") { - for (const item of response.toolCalls || []) { - setStore( - produce((s) => { - s.prompt.push({ - role: "assistant", - content: [ - { - type: "tool-call", - toolName: item.toolName, - args: JSON.parse(item.args), - toolCallId: item.toolCallId, - }, - ], - }) - }), - ) - props.onPromptUpdated?.(store.prompt) - - const called = await props.tool.call({ - name: item.toolName, - arguments: JSON.parse(item.args), - }) - - setStore( - produce((s) => { - s.prompt.push({ - role: "tool", - content: [ - { - type: "tool-result", - toolName: item.toolName, - toolCallId: item.toolCallId, - result: called, - }, - ], - }) - }), - ) - props.onPromptUpdated?.(store.prompt) - } - } - continue - } - - if (response.err === "context") { - setStore( - produce((s) => { - s.prompt.splice(2, 1) - }), - ) - props.onPromptUpdated?.(store.prompt) - } - - if (response.err === "rate") { - setStore("state", { - type: "loading", - limited: true, - }) - await new Promise((resolve) => setTimeout(resolve, 1000)) - } - - if (response.err === "balance") { - setStore( - produce((s) => { - s.prompt.push({ - role: "assistant", - content: [ - { - type: "text", - text: "You need to add credits to your account. Please go to Billing and add credits to continue.", - }, - ], - }) - s.state = { type: "idle" } - }), - ) - props.onPromptUpdated?.(store.prompt) - break - } - } - setStore("state", { type: "idle" }) - }, - async cancel() { - abort.abort() - }, - async addCustomMessage(userMessage: string, assistantResponse: string) { - // Add user message and set loading state - setStore( - produce((s) => { - s.prompt.push({ - role: "user", - content: [ - { - type: "text", - text: userMessage, - }, - ], - }) - s.state = { - type: "loading", - limited: false, - } - }), - ) - props.onPromptUpdated?.(store.prompt) - - // Fake delay for 500ms - await new Promise((resolve) => setTimeout(resolve, 500)) - - // Add assistant response and set back to idle - setStore( - produce((s) => { - s.prompt.push({ - role: "assistant", - content: [ - { - type: "text", - text: assistantResponse, - }, - ], - }) - s.state = { type: "idle" } - }), - ) - props.onPromptUpdated?.(store.prompt) - }, - } -} diff --git a/cloud/web/src/pages/[workspace]/index.module.css b/cloud/web/src/pages/[workspace]/index.module.css deleted file mode 100644 index 0037d97ff..000000000 --- a/cloud/web/src/pages/[workspace]/index.module.css +++ /dev/null @@ -1,239 +0,0 @@ -.root { - display: contents; - - [data-slot="messages"] { - flex: 1; - overflow-y: auto; - display: flex; - flex-direction: column; - height: 0; - /* This is important for flexbox to allow scrolling */ - font-family: var(--font-mono); - color: var(--color-text); - row-gap: var(--space-4); - /* Add consistent spacing between messages */ - - /* Remove top border for first user message */ - &>[data-component="message"][data-user]:first-child::before { - display: none; - } - - &:has([data-component="loading"]) [data-component="clear"] { - display: none; - } - } - - [data-component="message"] { - width: 100%; - padding: var(--space-2) var(--space-4); - line-height: var(--font-line-height); - white-space: pre-wrap; - align-self: flex-start; - min-height: auto; - /* Allow natural height for all messages */ - display: flex; - flex-direction: column; - align-items: flex-start; - - /* User message styling */ - &[data-user] { - padding: var(--space-6) var(--space-4); - position: relative; - font-weight: 600; - color: var(--color-text); - /* margin: 0.5rem 0; */ - } - - &[data-user]::before, - &[data-user]::after { - content: ""; - position: absolute; - left: var(--space-4); - right: var(--space-4); - height: var(--space-px); - background-color: var(--color-border); - z-index: 1; - /* Ensure borders appear above other content */ - } - - &[data-user]::before { - top: 0; - } - - &[data-user]::after { - bottom: 0; - } - - &[data-assistant] { - color: var(--color-text); - } - } - - [data-component="tool"] { - display: flex; - width: 100%; - padding: 0 var(--space-4); - margin-left: 0; - flex-direction: column; - opacity: 0.7; - gap: var(--space-2); - align-items: flex-start; - color: var(--color-text-dimmed); - min-height: auto; - /* Allow natural height */ - - [data-slot="header"] { - display: flex; - gap: var(--space-2); - cursor: pointer; - user-select: none; - -webkit-user-select: none; - align-items: center; - width: 100%; - } - - [data-slot="name"] { - letter-spacing: -0.03125rem; - text-transform: uppercase; - font-weight: 500; - font-size: var(--font-size-sm); - } - - [data-slot="expand"] { - font-size: var(--font-size-sm); - } - - [data-slot="content"] { - padding: 0; - line-height: var(--font-line-height); - font-size: var(--font-size-sm); - white-space: pre-wrap; - display: none; - width: 100%; - } - - [data-slot="output"] { - margin-top: var(--space-1); - } - - &[data-expanded="true"] [data-slot="content"] { - display: block; - } - - &[data-expanded="true"] [data-slot="expand"] { - transform: rotate(45deg); - } - } - - [data-component="loading"] { - padding: var(--space-4) var(--space-4) var(--space-8); - height: 1.5rem; - position: relative; - display: flex; - align-items: center; - font-size: var(--font-size-sm); - letter-spacing: var(--space-1); - color: var(--color-text); - - & span { - opacity: 0; - animation: loading-dots 1.4s linear infinite; - } - - & span:nth-child(2) { - animation-delay: 0.2s; - } - - & span:nth-child(3) { - animation-delay: 0.4s; - } - } - - [data-component="clear"] { - position: relative; - padding: var(--space-4) var(--space-4); - - &::before { - content: ""; - position: absolute; - left: var(--space-4); - right: var(--space-4); - top: 0; - height: var(--space-px); - background-color: var(--color-border); - z-index: 1; - } - - & [data-component="button"] { - padding-left: 0; - } - } - - [data-slot="footer"] { - display: flex; - flex-direction: column; - padding: 0; - border-top: 2px solid var(--color-border); - position: sticky; - bottom: 0; - z-index: 10; - /* Ensure it's above other content */ - margin-top: auto; - /* Push to bottom if content is short */ - width: 100%; - } - - [data-component="chat"] { - display: flex; - padding: var(--space-0-5) 0; - align-items: center; - width: 100%; - height: 100%; - - textarea { - --padding-y: var(--space-4); - --line-height: 1.5; - --text-height: calc(var(--line-height) * var(--font-size-lg)); - --height: calc(var(--text-height) + var(--padding-y) * 2); - - width: 100%; - resize: none; - line-height: var(--line-height); - height: var(--height); - min-height: var(--height); - max-height: calc(5 * var(--text-height) + var(--padding-y) * 2); - padding: var(--padding-y) var(--space-4); - border-radius: 0; - background-color: transparent; - color: var(--color-text); - border: none; - outline: none; - font-size: var(--font-size-lg); - } - - textarea::placeholder { - color: var(--color-text-dimmed); - opacity: 0.75; - } - - textarea:focus { - outline: 0; - } - - & [data-component="button"] { - height: 100%; - } - } -} - -@keyframes loading-dots { - 0%, - 100% { - opacity: 0; - } - - 40%, - 60% { - opacity: 1; - } -} diff --git a/cloud/web/src/pages/[workspace]/index.tsx b/cloud/web/src/pages/[workspace]/index.tsx deleted file mode 100644 index 50c58ee30..000000000 --- a/cloud/web/src/pages/[workspace]/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Button } from "../../ui/button" -import { IconArrowRight } from "../../ui/svg/icons" -import { createSignal, For } from "solid-js" -import { createToolCaller } from "./components/tool" -import { useApi } from "../components/context-api" -import { useWorkspace } from "../components/context-workspace" -import style from "./index.module.css" - -export default function Index() { - const api = useApi() - const workspace = useWorkspace() - - return ( - <div class={style.root}> - <h1>Hello</h1> - </div> - ) -} diff --git a/cloud/web/src/pages/[workspace]/keys.module.css b/cloud/web/src/pages/[workspace]/keys.module.css deleted file mode 100644 index 4ae2989be..000000000 --- a/cloud/web/src/pages/[workspace]/keys.module.css +++ /dev/null @@ -1,97 +0,0 @@ -.root { - display: flex; - flex-direction: column; - gap: 2rem; -} - -.root [data-slot="keys-info"] { - display: flex; - flex-direction: column; - gap: 1rem; -} - -.root [data-slot="header"] { - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.root [data-slot="header"] h2 { - margin: 0; - font-size: 1.5rem; - font-weight: 600; -} - -.root [data-slot="header"] p { - margin: 0; - color: var(--color-text-secondary); -} - -.root [data-slot="key-list"] { - display: flex; - flex-direction: column; - gap: 1rem; -} - -.root [data-slot="key-item"] { - display: flex; - align-items: center; - justify-content: space-between; - padding: 1rem; - border: 1px solid var(--color-border); - border-radius: 0.5rem; - background: var(--color-background-secondary); -} - -.root [data-slot="key-actions"] { - display: flex; - gap: 0.5rem; -} - -.root [data-slot="key-info"] { - display: flex; - flex-direction: column; - gap: 0.25rem; -} - -.root [data-slot="key-value"] { - font-family: monospace; - font-size: 0.875rem; - color: var(--color-text-primary); -} - -.root [data-slot="key-meta"] { - font-size: 0.75rem; - color: var(--color-text-secondary); -} - -.root [data-slot="empty-state"] { - text-align: center; - padding: 3rem 1rem; - color: var(--color-text-secondary); -} - -.root [data-slot="actions"] { - display: flex; - align-items: center; - justify-content: space-between; -} - -.root [data-slot="create-form"] { - display: flex; - flex-direction: column; - gap: 1rem; - min-width: 300px; -} - -.root [data-slot="form-actions"] { - display: flex; - gap: 0.5rem; -} - -.root [data-slot="key-name"] { - font-weight: 600; - font-size: 1rem; - color: var(--color-text-primary); - margin-bottom: 0.25rem; -} diff --git a/cloud/web/src/pages/[workspace]/keys.tsx b/cloud/web/src/pages/[workspace]/keys.tsx deleted file mode 100644 index e5b192a2b..000000000 --- a/cloud/web/src/pages/[workspace]/keys.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import { Button } from "../../ui/button" -import { useApi } from "../components/context-api" -import { createSignal, createResource, For, Show } from "solid-js" -import style from "./keys.module.css" - -export default function Keys() { - const api = useApi() - const [isCreating, setIsCreating] = createSignal(false) - const [showCreateForm, setShowCreateForm] = createSignal(false) - const [keyName, setKeyName] = createSignal("") - - const [keysData, { refetch }] = createResource(async () => { - const response = await api.keys.$get() - return response.json() - }) - - const handleCreateKey = async () => { - if (!keyName().trim()) return - - try { - setIsCreating(true) - await api.keys.$post({ - json: { name: keyName().trim() }, - }) - refetch() - setKeyName("") - setShowCreateForm(false) - } catch (error) { - console.error("Failed to create API key:", error) - } finally { - setIsCreating(false) - } - } - - const handleDeleteKey = async (keyId: string) => { - if (!confirm("Are you sure you want to delete this API key? This action cannot be undone.")) { - return - } - - try { - await api.keys[":id"].$delete({ - param: { id: keyId }, - }) - refetch() - } catch (error) { - console.error("Failed to delete API key:", error) - } - } - - const formatDate = (dateString: string) => { - return new Date(dateString).toLocaleDateString() - } - - const formatKey = (key: string) => { - if (key.length <= 11) return key - return `${key.slice(0, 7)}...${key.slice(-4)}` - } - - const copyToClipboard = async (text: string) => { - try { - await navigator.clipboard.writeText(text) - } catch (error) { - console.error("Failed to copy to clipboard:", error) - } - } - - return ( - <> - <div data-component="title-bar"> - <div data-slot="left"> - <h1>API Keys</h1> - </div> - </div> - <div class={style.root} data-max-width data-max-width-64> - <div data-slot="keys-info"> - <div data-slot="actions"> - <div data-slot="header"> - <h2>API Keys</h2> - <p>Manage your API keys to access the OpenCode gateway.</p> - </div> - <Show - when={!showCreateForm()} - fallback={ - <div data-slot="create-form"> - <input - data-component="input" - type="text" - placeholder="Enter key name" - value={keyName()} - onInput={(e) => setKeyName(e.currentTarget.value)} - onKeyPress={(e) => e.key === "Enter" && handleCreateKey()} - /> - <div data-slot="form-actions"> - <Button color="primary" disabled={isCreating() || !keyName().trim()} onClick={handleCreateKey}> - {isCreating() ? "Creating..." : "Create"} - </Button> - <Button - color="ghost" - onClick={() => { - setShowCreateForm(false) - setKeyName("") - }} - > - Cancel - </Button> - </div> - </div> - } - > - <Button color="primary" onClick={() => setShowCreateForm(true)}> - Create API Key - </Button> - </Show> - </div> - - <div data-slot="key-list"> - <For - each={keysData()?.keys} - fallback={ - <div data-slot="empty-state"> - <p>Create an API key to access opencode gateway</p> - </div> - } - > - {(key) => ( - <div data-slot="key-item"> - <div data-slot="key-info"> - <div data-slot="key-name">{key.name}</div> - <div data-slot="key-value">{formatKey(key.key)}</div> - <div data-slot="key-meta"> - Created: {formatDate(key.timeCreated)} - {key.timeUsed && ` • Last used: ${formatDate(key.timeUsed)}`} - </div> - </div> - <div data-slot="key-actions"> - <Button color="ghost" onClick={() => copyToClipboard(key.key)} title="Copy API key"> - Copy - </Button> - <Button color="ghost" onClick={() => handleDeleteKey(key.id)} title="Delete API key"> - Delete - </Button> - </div> - </div> - )} - </For> - </div> - </div> - </div> - </> - ) -} diff --git a/cloud/web/src/pages/components/context-api.tsx b/cloud/web/src/pages/components/context-api.tsx deleted file mode 100644 index 0a348f48f..000000000 --- a/cloud/web/src/pages/components/context-api.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { hc } from "hono/client" -import { ApiType } from "@opencode/cloud-function/src/gateway" -import { useWorkspace } from "./context-workspace" -import { useOpenAuth } from "../../components/context-openauth" - -export function useApi() { - const workspace = useWorkspace() - const auth = useOpenAuth() - return hc<ApiType>(import.meta.env.VITE_API_URL, { - async fetch(...args: Parameters<typeof fetch>): Promise<Response> { - const [input, init] = args - const request = input instanceof Request ? input : new Request(input, init) - const headers = new Headers(request.headers) - headers.set("authorization", `Bearer ${await auth.access()}`) - headers.set("x-opencode-workspace", workspace.id) - return fetch( - new Request(request, { - ...init, - headers, - }), - ) - }, - }) -} diff --git a/cloud/web/src/pages/components/context-workspace.tsx b/cloud/web/src/pages/components/context-workspace.tsx deleted file mode 100644 index 6bad39840..000000000 --- a/cloud/web/src/pages/components/context-workspace.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useNavigate, useParams } from "@solidjs/router" -import { createInitializedContext } from "../../util/context" -import { useAccount } from "../../components/context-account" -import { createEffect, createMemo } from "solid-js" - -export const { use: useWorkspace, provider: WorkspaceProvider } = - createInitializedContext("WorkspaceProvider", () => { - const params = useParams() - const account = useAccount() - const workspace = createMemo(() => - account.current?.workspaces.find( - (x) => x.id === params.workspace || x.slug === params.workspace, - ), - ) - const nav = useNavigate() - - createEffect(() => { - if (!workspace()) nav("/") - }) - - const result = () => workspace()! - result.ready = true - - return { - get id() { - return workspace()!.id - }, - get slug() { - return workspace()!.slug - }, - get name() { - return workspace()!.name - }, - get ready() { - return workspace() !== undefined - }, - } - }) diff --git a/cloud/web/src/pages/components/layout.module.css b/cloud/web/src/pages/components/layout.module.css deleted file mode 100644 index c64faa18e..000000000 --- a/cloud/web/src/pages/components/layout.module.css +++ /dev/null @@ -1,199 +0,0 @@ -.root { - --padding: var(--space-10); - --vertical-padding: var(--space-8); - --heading-font-size: var(--font-size-4xl); - --sidebar-width: 200px; - --mobile-breakpoint: 40rem; - --topbar-height: 60px; - - margin: var(--space-4); - border: 2px solid var(--color-border); - height: calc(100vh - var(--space-8)); - display: flex; - flex-direction: row; - overflow: hidden; - /* Prevent overall scrolling */ - position: relative; -} - -[data-component="mobile-top-bar"] { - display: none; - position: fixed; - top: 0; - left: 0; - right: 0; - height: var(--topbar-height); - background: var(--color-background); - border-bottom: 2px solid var(--color-border); - z-index: 20; - align-items: center; - padding: 0 var(--space-4) 0 0; - - [data-slot="logo"] { - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - - div { - text-transform: uppercase; - font-weight: 600; - letter-spacing: -0.03125rem; - } - - svg { - height: 28px; - width: auto; - color: var(--color-white); - } - } - - [data-slot="toggle"] { - background: transparent; - border: none; - padding: var(--space-4); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - - & svg { - width: 24px; - height: 24px; - color: var(--color-foreground); - } - } -} - -[data-component="sidebar"] { - width: var(--sidebar-width); - border-right: 2px solid var(--color-border); - display: flex; - flex-direction: column; - padding: calc(var(--padding) / 2); - overflow-y: auto; - /* Allow scrolling if needed */ - position: sticky; - top: 0; - height: 100%; - background-color: var(--color-background); - z-index: 10; - - [data-slot="logo"] { - margin-top: 2px; - margin-bottom: var(--space-7); - color: var(--color-white); - - & svg { - height: 32px; - width: auto; - } - } - - [data-slot="nav"] { - flex: 1; - - ul { - list-style-type: none; - padding: 0; - } - - li { - margin-bottom: calc(var(--vertical-padding) / 2); - text-transform: uppercase; - font-weight: 500; - } - - a { - display: block; - padding: var(--space-2) 0; - } - } - - [data-slot="user"] { - [data-component="button"] { - padding-left: 0; - padding-bottom: 0; - height: auto; - } - } -} - -.navActiveLink { - cursor: default; - text-decoration: none; -} - -[data-slot="main-content"] { - flex: 1; - display: flex; - flex-direction: column; - height: 100%; - /* Full height */ - overflow: hidden; - /* Prevent overflow */ - position: relative; - /* For positioning footer */ - width: 100%; - /* Full width */ -} - -/* Backdrop for mobile */ -[data-component="backdrop"] { - display: none; - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - /* background-color: rgba(0, 0, 0, 0.5); */ - z-index: 25; - backdrop-filter: blur(2px); -} - -/* Mobile styles */ -@media (max-width: 40rem) { - .root { - margin: 0; - border: none; - height: 100vh; - } - - [data-component="mobile-top-bar"] { - display: flex; - } - - [data-component="backdrop"] { - display: block; - } - - [data-component="sidebar"] { - position: fixed; - left: -100%; - top: 0; - height: 100vh; - width: 80%; - max-width: 280px; - transition: left 0.3s ease-in-out; - box-shadow: none; - z-index: 30; - padding: var(--space-8); - background-color: var(--color-bg); - - &[data-opened="true"] { - left: 0; - box-shadow: 8px 0 0px 0px var(--color-gray-4); - } - } - - [data-slot="main-content"] { - padding-top: var(--topbar-height); - /* Add space for the top bar */ - overflow-y: auto; - } - - /* Hide the logo in the sidebar on mobile since it's in the top bar */ - [data-component="sidebar"] [data-slot="logo"] { - display: none; - } -} diff --git a/cloud/web/src/pages/components/layout.tsx b/cloud/web/src/pages/components/layout.tsx deleted file mode 100644 index 711ed8fc2..000000000 --- a/cloud/web/src/pages/components/layout.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import style from "./layout.module.css" -import { useAccount } from "../../components/context-account" -import { Button } from "../../ui/button" -import { IconLogomark } from "../../ui/svg" -import { IconBars3BottomLeft } from "../../ui/svg/icons" -import { ParentProps, createMemo, createSignal } from "solid-js" -import { A, useLocation } from "@solidjs/router" -import { useOpenAuth } from "../../components/context-openauth" - -export default function Layout(props: ParentProps) { - const auth = useOpenAuth() - const account = useAccount() - const [sidebarOpen, setSidebarOpen] = createSignal(false) - const location = useLocation() - - const workspaceId = createMemo(() => account.current?.workspaces[0].id) - const pageTitle = createMemo(() => { - const path = location.pathname - if (path.endsWith("/billing")) return "Billing" - if (path.endsWith("/keys")) return "API Keys" - return null - }) - - function handleLogout() { - auth.logout(auth.subject?.id!) - } - - return ( - <div class={style.root}> - {/* Mobile top bar */} - <div data-component="mobile-top-bar"> - <button data-slot="toggle" onClick={() => setSidebarOpen(!sidebarOpen())}> - <IconBars3BottomLeft /> - </button> - - <div data-slot="logo"> - {pageTitle() ? ( - <div>{pageTitle()}</div> - ) : ( - <A href="/"> - <IconLogomark /> - </A> - )} - </div> - </div> - - {/* Backdrop for mobile sidebar - closes sidebar when clicked */} - {sidebarOpen() && <div data-component="backdrop" onClick={() => setSidebarOpen(false)}></div>} - - <div data-component="sidebar" data-opened={sidebarOpen() ? "true" : "false"}> - <div data-slot="logo"> - <A href="/"> - <IconLogomark /> - </A> - </div> - - <nav data-slot="nav"> - <ul> - <li> - <A end activeClass={style.navActiveLink} href={`/${workspaceId()}`} onClick={() => setSidebarOpen(false)}> - Chat - </A> - </li> - <li> - <A - activeClass={style.navActiveLink} - href={`/${workspaceId()}/billing`} - onClick={() => setSidebarOpen(false)} - > - Billing - </A> - </li> - <li> - <A - activeClass={style.navActiveLink} - href={`/${workspaceId()}/keys`} - onClick={() => setSidebarOpen(false)} - > - API Keys - </A> - </li> - </ul> - </nav> - - <div data-slot="user"> - <Button color="ghost" onClick={handleLogout} title={account.current?.email || ""}> - Logout - </Button> - </div> - </div> - - {/* Main Content */} - <div data-slot="main-content">{props.children}</div> - </div> - ) -} diff --git a/cloud/web/src/pages/index.tsx b/cloud/web/src/pages/index.tsx deleted file mode 100644 index 116ed156c..000000000 --- a/cloud/web/src/pages/index.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Match, Switch } from "solid-js" -import { useAccount } from "../components/context-account" -import { Navigate } from "@solidjs/router" -import { IconLogo } from "../ui/svg" -import styles from "./lander.module.css" -import { useOpenAuth } from "../components/context-openauth" - -export default function Index() { - const auth = useOpenAuth() - const account = useAccount() - return ( - <Switch> - <Match when={account.current}> - <Navigate href={`/${account.current!.workspaces[0].id}`} /> - </Match> - <Match when={!account.current}> - <div class={styles.lander}> - <div data-slot="hero"> - <section data-slot="top"> - <div data-slot="logo"> - <IconLogo /> - </div> - <h1>opencode Gateway Console</h1> - </section> - - <section data-slot="cta"> - <div> - <span onClick={() => auth.authorize({ provider: "github" })}>Sign in with GitHub</span> - </div> - <div> - <span onClick={() => auth.authorize({ provider: "google" })}>Sign in with Google</span> - </div> - </section> - </div> - </div> - </Match> - </Switch> - ) -} diff --git a/cloud/web/src/pages/lander.module.css b/cloud/web/src/pages/lander.module.css deleted file mode 100644 index 251e243fb..000000000 --- a/cloud/web/src/pages/lander.module.css +++ /dev/null @@ -1,83 +0,0 @@ -.lander { - --padding: 3rem; - --vertical-padding: 2rem; - --heading-font-size: 2rem; - - margin: 1rem; - - @media (max-width: 30rem) { - & { - --padding: 1.5rem; - --vertical-padding: 1rem; - --heading-font-size: 1.5rem; - - margin: 0.5rem; - } - } - - [data-slot="hero"] { - border: 2px solid var(--color-border); - - max-width: 64rem; - margin-left: auto; - margin-right: auto; - width: 100%; - } - - [data-slot="top"] { - padding: var(--padding); - - h1 { - margin-top: calc(var(--vertical-padding) / 8); - font-size: var(--heading-font-size); - line-height: 1.25; - text-transform: uppercase; - font-weight: 600; - } - - [data-slot="logo"] { - width: clamp(200px, 70vw, 400px); - color: var(--color-white); - } - } - - [data-slot="cta"] { - display: flex; - flex-direction: row; - justify-content: space-between; - border-top: 2px solid var(--color-border); - - & > div { - flex: 1; - line-height: 1.4; - text-align: center; - text-transform: uppercase; - cursor: pointer; - text-decoration: underline; - letter-spacing: -0.03125rem; - - &[data-slot="col-2"] { - background-color: var(--color-border); - color: var(--color-text-invert); - font-weight: 600; - } - - & > * { - display: block; - width: 100%; - height: 100%; - padding: calc(var(--padding) / 2) 0.5rem; - } - } - - @media (max-width: 30rem) { - & > div { - padding-bottom: calc(var(--padding) / 2 + 4px); - } - } - - & > div + div { - border-left: 2px solid var(--color-border); - } - } -} diff --git a/cloud/web/src/pages/test/design.module.css b/cloud/web/src/pages/test/design.module.css deleted file mode 100644 index fee4e3cd3..000000000 --- a/cloud/web/src/pages/test/design.module.css +++ /dev/null @@ -1,204 +0,0 @@ -.pageContainer { - padding: 2rem; - max-width: 1200px; - margin: 0 auto; -} - -.componentTable { - width: 100%; - border-collapse: collapse; - table-layout: fixed; - border: 2px solid var(--color-border); -} - -.componentCell { - padding: 1rem; - border: 2px solid var(--color-border); - vertical-align: top; -} - -.componentLabel { - text-transform: uppercase; - letter-spacing: -0.03125rem; - font-size: 0.85rem; - font-weight: 500; - margin-bottom: 0.75rem; - color: var(--color-text-dimmed); -} - -.sectionTitle { - margin-bottom: 1rem; - text-transform: uppercase; - letter-spacing: -0.03125rem; - font-size: 1.2rem; -} - -.divider { - height: 2px; - background: var(--color-border); - margin: 3rem 0; - width: 100%; -} - -.header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 2rem; -} - -.buttonSection { - margin-bottom: 4rem; -} - -.colorSection { - margin-bottom: 4rem; -} - -.labelSection { - margin-bottom: 4rem; -} - -.inputSection { - margin-bottom: 4rem; -} - -.dialogSection { - margin-bottom: 4rem; -} - -.formGroup { - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.dialogContent { - padding: 2rem; -} - -.dialogContentFooter { - margin-top: 1rem; -} - -.pageTitle { - font-size: var(--heading-font-size, 2rem); - text-transform: uppercase; - font-weight: 600; -} - -.colorBox { - width: 100%; - height: 80px; - margin-bottom: 0.5rem; - position: relative; - display: flex; - align-items: flex-end; - justify-content: center; - padding-bottom: 0.5rem; -} - -.colorOrange { - background-color: var(--color-orange); -} - -.colorOrangeLow { - background-color: var(--color-orange-low); -} - -.colorOrangeHigh { - background-color: var(--color-orange-high); -} - -.colorGreen { - background-color: var(--color-green); -} - -.colorGreenLow { - background-color: var(--color-green-low); -} - -.colorGreenHigh { - background-color: var(--color-green-high); -} - -.colorBlue { - background-color: var(--color-blue); -} - -.colorBlueLow { - background-color: var(--color-blue-low); -} - -.colorBlueHigh { - background-color: var(--color-blue-high); -} - -.colorPurple { - background-color: var(--color-purple); -} - -.colorPurpleLow { - background-color: var(--color-purple-low); -} - -.colorPurpleHigh { - background-color: var(--color-purple-high); -} - -.colorRed { - background-color: var(--color-red); -} - -.colorRedLow { - background-color: var(--color-red-low); -} - -.colorRedHigh { - background-color: var(--color-red-high); -} - -.colorAccent { - background-color: var(--color-accent); -} - -.colorAccentLow { - background-color: var(--color-accent-low); -} - -.colorAccentHigh { - background-color: var(--color-accent-high); -} - -.colorCode { - background-color: rgba(0, 0, 0, 0.5); - color: white; - padding: 4px 8px; - border-radius: 4px; - font-size: 0.8rem; - font-family: monospace; -} - -.colorVariants { - display: flex; - gap: 0.5rem; -} - -.colorVariant { - flex: 1; - height: 40px; - position: relative; - display: flex; - align-items: center; - justify-content: center; -} - -.colorVariantCode { - background-color: rgba(0, 0, 0, 0.5); - color: white; - padding: 2px 4px; - border-radius: 4px; - font-size: 0.65rem; - font-family: monospace; - white-space: nowrap; -} diff --git a/cloud/web/src/pages/test/design.tsx b/cloud/web/src/pages/test/design.tsx deleted file mode 100644 index 3bf759316..000000000 --- a/cloud/web/src/pages/test/design.tsx +++ /dev/null @@ -1,562 +0,0 @@ -import { Button } from "../../ui/button" -import { Dialog } from "../../ui/dialog" -import { Navigate } from "@solidjs/router" -import { createSignal, Show } from "solid-js" -import { IconHome, IconPencilSquare } from "../../ui/svg/icons" -import { useTheme } from "../../components/context-theme" -import { useDialog } from "../../ui/context-dialog" -import { DialogString } from "../../ui/dialog-string" -import { DialogSelect } from "../../ui/dialog-select" -import styles from "./design.module.css" - -export default function DesignSystem() { - const dialog = useDialog() - const [dialogOpen, setDialogOpen] = createSignal(false) - const [dialogOpenTransition, setDialogOpenTransition] = createSignal(false) - const theme = useTheme() - - // Check if we're running locally - const isLocal = import.meta.env.DEV === true - - if (!isLocal) { - return <Navigate href="/" /> - } - - // Add a toggle button for theme - const toggleTheme = () => { - theme.setMode(theme.mode === "light" ? "dark" : "light") - } - - return ( - <div class={styles.pageContainer}> - <div class={styles.header}> - <h1 class={styles.pageTitle}>Design System</h1> - <Button onClick={toggleTheme}> - Toggle {theme.mode === "light" ? "Dark" : "Light"} Mode - </Button> - </div> - - <section class={styles.colorSection}> - <h2 class={styles.sectionTitle}>Colors</h2> - - <table class={styles.componentTable}> - <tbody> - <tr> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Orange</h3> - <div class={`${styles.colorBox} ${styles.colorOrange}`}> - <span class={styles.colorCode}>hsl(41, 82%, 63%)</span> - </div> - <div class={styles.colorVariants}> - <div - class={`${styles.colorVariant} ${styles.colorOrangeLow}`} - > - <span class={styles.colorVariantCode}> - hsl(41, 39%, 22%) - </span> - </div> - <div - class={`${styles.colorVariant} ${styles.colorOrangeHigh}`} - > - <span class={styles.colorVariantCode}> - hsl(41, 82%, 87%) - </span> - </div> - </div> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Green</h3> - <div class={`${styles.colorBox} ${styles.colorGreen}`}> - <span class={styles.colorCode}>hsl(101, 82%, 63%)</span> - </div> - <div class={styles.colorVariants}> - <div class={`${styles.colorVariant} ${styles.colorGreenLow}`}> - <span class={styles.colorVariantCode}> - hsl(101, 39%, 22%) - </span> - </div> - <div - class={`${styles.colorVariant} ${styles.colorGreenHigh}`} - > - <span class={styles.colorVariantCode}> - hsl(101, 82%, 80%) - </span> - </div> - </div> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Blue</h3> - <div class={`${styles.colorBox} ${styles.colorBlue}`}> - <span class={styles.colorCode}>hsl(234, 100%, 60%)</span> - </div> - <div class={styles.colorVariants}> - <div class={`${styles.colorVariant} ${styles.colorBlueLow}`}> - <span class={styles.colorVariantCode}> - hsl(234, 54%, 20%) - </span> - </div> - <div class={`${styles.colorVariant} ${styles.colorBlueHigh}`}> - <span class={styles.colorVariantCode}> - hsl(234, 100%, 87%) - </span> - </div> - </div> - </td> - </tr> - <tr> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Purple</h3> - <div class={`${styles.colorBox} ${styles.colorPurple}`}> - <span class={styles.colorCode}>hsl(281, 82%, 63%)</span> - </div> - <div class={styles.colorVariants}> - <div - class={`${styles.colorVariant} ${styles.colorPurpleLow}`} - > - <span class={styles.colorVariantCode}> - hsl(281, 39%, 22%) - </span> - </div> - <div - class={`${styles.colorVariant} ${styles.colorPurpleHigh}`} - > - <span class={styles.colorVariantCode}> - hsl(281, 82%, 89%) - </span> - </div> - </div> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Red</h3> - <div class={`${styles.colorBox} ${styles.colorRed}`}> - <span class={styles.colorCode}>hsl(339, 82%, 63%)</span> - </div> - <div class={styles.colorVariants}> - <div class={`${styles.colorVariant} ${styles.colorRedLow}`}> - <span class={styles.colorVariantCode}> - hsl(339, 39%, 22%) - </span> - </div> - <div class={`${styles.colorVariant} ${styles.colorRedHigh}`}> - <span class={styles.colorVariantCode}> - hsl(339, 82%, 87%) - </span> - </div> - </div> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Accent</h3> - <div class={`${styles.colorBox} ${styles.colorAccent}`}> - <span class={styles.colorCode}>hsl(13, 88%, 57%)</span> - </div> - <div class={styles.colorVariants}> - <div - class={`${styles.colorVariant} ${styles.colorAccentLow}`} - > - <span class={styles.colorVariantCode}> - hsl(13, 75%, 30%) - </span> - </div> - <div - class={`${styles.colorVariant} ${styles.colorAccentHigh}`} - > - <span class={styles.colorVariantCode}> - hsl(13, 100%, 78%) - </span> - </div> - </div> - </td> - </tr> - </tbody> - </table> - </section> - - <div class={styles.divider}></div> - - <section class={styles.buttonSection}> - <h2 class={styles.sectionTitle}>Buttons</h2> - - <table class={styles.componentTable}> - <tbody> - <tr> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Primary</h3> - <Button>Primary Button</Button> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Secondary</h3> - <Button color="secondary">Secondary Button</Button> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Ghost</h3> - <Button color="ghost">Ghost Button</Button> - </td> - </tr> - <tr> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Primary Disabled</h3> - <Button disabled>Primary Button</Button> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Secondary Disabled</h3> - <Button color="secondary" disabled> - Secondary Button - </Button> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Ghost Disabled</h3> - <Button color="ghost" disabled> - Ghost Button - </Button> - </td> - </tr> - <tr> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Small</h3> - <Button size="sm">Small Button</Button> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Small Secondary</h3> - <Button size="sm" color="secondary"> - Small Secondary - </Button> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Small Ghost</h3> - <Button size="sm" color="ghost"> - Small Ghost - </Button> - </td> - </tr> - <tr> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>With Icon</h3> - <Button icon={<IconHome />}>With Icon</Button> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Icon + Secondary</h3> - <Button icon={<IconHome />} color="secondary"> - Icon Secondary - </Button> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Icon + Ghost</h3> - <Button icon={<IconHome />} color="ghost"> - Icon Ghost - </Button> - </td> - </tr> - <tr> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Small + Icon</h3> - <Button size="sm" icon={<IconHome />}> - Small Icon - </Button> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Small + Icon + Secondary</h3> - <Button size="sm" icon={<IconHome />} color="secondary"> - Small Icon Secondary - </Button> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Small + Icon + Ghost</h3> - <Button size="sm" icon={<IconHome />} color="ghost"> - Small Icon Ghost - </Button> - </td> - </tr> - <tr> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Icon Only</h3> - <Button icon={<IconHome />}></Button> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Icon Only + Secondary</h3> - <Button icon={<IconHome />} color="secondary"></Button> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Icon Only + Ghost</h3> - <Button icon={<IconHome />} color="ghost"></Button> - </td> - </tr> - <tr> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Icon Only Disabled</h3> - <Button icon={<IconHome />} disabled></Button> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}> - Icon Only + Secondary Disabled - </h3> - <Button icon={<IconHome />} color="secondary" disabled></Button> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}> - Icon Only + Ghost Disabled - </h3> - <Button icon={<IconHome />} color="ghost" disabled></Button> - </td> - </tr> - <tr> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Small Icon Only</h3> - <Button size="sm" icon={<IconHome />}></Button> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}> - Small Icon Only + Secondary - </h3> - <Button - size="sm" - icon={<IconHome />} - color="secondary" - ></Button> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Small Icon Only + Ghost</h3> - <Button size="sm" icon={<IconHome />} color="ghost"></Button> - </td> - </tr> - </tbody> - </table> - </section> - - <div class={styles.divider}></div> - - <section class={styles.labelSection}> - <h2 class={styles.sectionTitle}>Labels</h2> - - <table class={styles.componentTable}> - <tbody> - <tr> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Small</h3> - <label data-size="sm" data-component="label"> - Small Label Text - </label> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Medium</h3> - <label data-size="md" data-component="label"> - Medium Label Text - </label> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Large</h3> - <label data-size="lg" data-component="label"> - Large Label Text - </label> - </td> - </tr> - </tbody> - </table> - </section> - - <div class={styles.divider}></div> - - <section class={styles.inputSection}> - <h2 class={styles.sectionTitle}>Inputs</h2> - - <table class={styles.componentTable}> - <tbody> - <tr> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Small</h3> - <input - data-component="input" - data-size="sm" - placeholder="Small input field" - /> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Medium</h3> - <input - data-component="input" - data-size="md" - placeholder="Medium input field" - /> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Large</h3> - <input - data-component="input" - data-size="lg" - placeholder="Large input field" - /> - </td> - </tr> - <tr> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Disabled</h3> - <input - data-component="input" - data-size="md" - placeholder="Disabled input" - disabled - /> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>With Value</h3> - <input - data-component="input" - data-size="md" - value="Input with preset value" - readOnly - /> - </td> - </tr> - </tbody> - </table> - </section> - - <div class={styles.divider}></div> - - <section class={styles.dialogSection}> - <h2 class={styles.sectionTitle}>Dialogs</h2> - - <table class={styles.componentTable}> - <tbody> - <tr> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Default</h3> - <Button color="secondary" onClick={() => setDialogOpen(true)}> - Open Dialog - </Button> - <Dialog open={dialogOpen()} onOpenChange={setDialogOpen}> - <div data-slot="header"> - <div data-slot="title">Dialog Title</div> - </div> - <div data-slot="main"> - <p>This is the default dialog content.</p> - </div> - <div data-slot="footer"> - <Button onClick={() => setDialogOpen(false)}>Close</Button> - </div> - </Dialog> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Small With Transition</h3> - <Button - color="secondary" - onClick={() => { - setDialogOpenTransition(true) - }} - > - Small Dialog - </Button> - <Dialog - open={dialogOpenTransition()} - onOpenChange={setDialogOpenTransition} - size="sm" - transition={true} - > - <div class={styles.dialogContent}> - <h2 class={styles.sectionTitle}>Small Dialog</h2> - <p>This is a smaller dialog with transitions.</p> - <div class={styles.dialogContentFooter}> - <Button onClick={() => setDialogOpenTransition(false)}> - Close - </Button> - </div> - </div> - </Dialog> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Input String</h3> - <Button - color="secondary" - onClick={() => - dialog.open(DialogString, { - title: "Name", - action: "Change name", - placeholder: "Enter a name", - onSubmit: () => {}, - }) - } - > - String - </Button> - </td> - </tr> - <tr> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Select Input</h3> - <Button - color="secondary" - onClick={() => - dialog.open(DialogSelect, { - placeholder: "Select", - title: "User Settings", - options: [ - { - display: "Change name", - prefix: <IconPencilSquare />, - onSelect: () => { - dialog.close() - }, - }, - { - display: "Remove user", - prefix: <IconHome />, - onSelect: () => { - dialog.close() - }, - }, - ], - }) - } - > - Select - </Button> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Select Input</h3> - <Button - color="secondary" - onClick={() => - dialog.open(DialogSelect, { - placeholder: "Select", - title: "User Settings", - options: [ - { - display: "Change name", - onSelect: () => { - dialog.close() - }, - }, - { - display: "Remove user", - onSelect: () => { - dialog.close() - }, - }, - ], - }) - } - > - No Prefix - </Button> - </td> - <td class={styles.componentCell}> - <h3 class={styles.componentLabel}>Select No Options</h3> - <Button - color="secondary" - onClick={() => - dialog.open(DialogSelect, { - placeholder: "Select", - title: "User Settings", - options: [], - }) - } - > - No Options - </Button> - </td> - </tr> - </tbody> - </table> - </section> - </div> - ) -} |
