mirror of
https://github.com/aljazceru/opencode.git
synced 2025-12-21 09:44:21 +01:00
wip: desktop work
This commit is contained in:
140
packages/desktop/src/components/diff.tsx
Normal file
140
packages/desktop/src/components/diff.tsx
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import { type FileContents, FileDiff, type DiffLineAnnotation } from "@pierre/precision-diffs"
|
||||||
|
|
||||||
|
export interface DiffProps {
|
||||||
|
before: FileContents
|
||||||
|
after: FileContents
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Diff(props: DiffProps) {
|
||||||
|
let container!: HTMLDivElement
|
||||||
|
|
||||||
|
console.log(props)
|
||||||
|
|
||||||
|
interface ThreadMetadata {
|
||||||
|
threadId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const lineAnnotations: DiffLineAnnotation<ThreadMetadata>[] = [
|
||||||
|
{
|
||||||
|
side: "additions",
|
||||||
|
// The line number specified for an annotation is the visual line number
|
||||||
|
// you see in the number column of a diff
|
||||||
|
lineNumber: 16,
|
||||||
|
metadata: { threadId: "68b329da9893e34099c7d8ad5cb9c940" },
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const instance = new FileDiff<ThreadMetadata>({
|
||||||
|
// You can provide a 'theme' prop that maps to any
|
||||||
|
// built in shiki theme or you can register a custom
|
||||||
|
// theme. We also include 2 custom themes
|
||||||
|
//
|
||||||
|
// 'pierre-night' and 'pierre-light
|
||||||
|
//
|
||||||
|
// For the rest of the available shiki themes, check out:
|
||||||
|
// https://shiki.style/themes
|
||||||
|
theme: "none",
|
||||||
|
// Or can also provide a 'themes' prop, which allows the code to adapt
|
||||||
|
// to your OS light or dark theme
|
||||||
|
// themes: { dark: 'pierre-night', light: 'pierre-light' },
|
||||||
|
// When using the 'themes' prop, 'themeType' allows you to force 'dark'
|
||||||
|
// or 'light' theme, or inherit from the OS ('system') theme.
|
||||||
|
themeType: "system",
|
||||||
|
// Disable the line numbers for your diffs, generally not recommended
|
||||||
|
disableLineNumbers: false,
|
||||||
|
// Whether code should 'wrap' with long lines or 'scroll'.
|
||||||
|
overflow: "scroll",
|
||||||
|
// Normally you shouldn't need this prop, but if you don't provide a
|
||||||
|
// valid filename or your file doesn't have an extension you may want to
|
||||||
|
// override the automatic detection. You can specify that language here:
|
||||||
|
// https://shiki.style/languages
|
||||||
|
// lang?: SupportedLanguages;
|
||||||
|
// 'diffStyle' controls whether the diff is presented side by side or
|
||||||
|
// in a unified (single column) view
|
||||||
|
diffStyle: "split",
|
||||||
|
// Line decorators to help highlight changes.
|
||||||
|
// 'bars' (default):
|
||||||
|
// Shows some red-ish or green-ish (theme dependent) bars on the left
|
||||||
|
// edge of relevant lines
|
||||||
|
//
|
||||||
|
// 'classic':
|
||||||
|
// shows '+' characters on additions and '-' characters on deletions
|
||||||
|
//
|
||||||
|
// 'none':
|
||||||
|
// No special diff indicators are shown
|
||||||
|
diffIndicators: "bars",
|
||||||
|
// By default green-ish or red-ish background are shown on added and
|
||||||
|
// deleted lines respectively. Disable that feature here
|
||||||
|
disableBackground: false,
|
||||||
|
// Diffs are split up into hunks, this setting customizes what to show
|
||||||
|
// between each hunk.
|
||||||
|
//
|
||||||
|
// 'line-info' (default):
|
||||||
|
// Shows a bar that tells you how many lines are collapsed. If you are
|
||||||
|
// using the oldFile/newFile API then you can click those bars to
|
||||||
|
// expand the content between them
|
||||||
|
//
|
||||||
|
// 'metadata':
|
||||||
|
// Shows the content you'd see in a normal patch file, usually in some
|
||||||
|
// format like '@@ -60,6 +60,22 @@'. You cannot use these to expand
|
||||||
|
// hidden content
|
||||||
|
//
|
||||||
|
// 'simple':
|
||||||
|
// Just a subtle bar separator between each hunk
|
||||||
|
hunkSeparators: "line-info",
|
||||||
|
// On lines that have both additions and deletions, we can run a
|
||||||
|
// separate diff check to mark parts of the lines that change.
|
||||||
|
// 'none':
|
||||||
|
// Do not show these secondary highlights
|
||||||
|
//
|
||||||
|
// 'char':
|
||||||
|
// Show changes at a per character granularity
|
||||||
|
//
|
||||||
|
// 'word':
|
||||||
|
// Show changes but rounded up to word boundaries
|
||||||
|
//
|
||||||
|
// 'word-alt' (default):
|
||||||
|
// Similar to 'word', however we attempt to minimize single character
|
||||||
|
// gaps between highlighted changes
|
||||||
|
lineDiffType: "word-alt",
|
||||||
|
// If lines exceed these character lengths then we won't perform the
|
||||||
|
// line lineDiffType check
|
||||||
|
maxLineDiffLength: 1000,
|
||||||
|
// If any line in the diff exceeds this value then we won't attempt to
|
||||||
|
// syntax highlight the diff
|
||||||
|
maxLineLengthForHighlighting: 1000,
|
||||||
|
// Enabling this property will hide the file header with file name and
|
||||||
|
// diff stats.
|
||||||
|
disableFileHeader: false,
|
||||||
|
// You can optionally pass a render function for rendering out line
|
||||||
|
// annotations. Just return the dom node to render
|
||||||
|
renderAnnotation(annotation: DiffLineAnnotation<ThreadMetadata>): HTMLElement {
|
||||||
|
// Despite the diff itself being rendered in the shadow dom,
|
||||||
|
// annotations are inserted via the web components 'slots' api and you
|
||||||
|
// can use all your normal normal css and styling for them
|
||||||
|
const element = document.createElement("div")
|
||||||
|
element.innerText = annotation.metadata.threadId
|
||||||
|
return element
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// If you ever want to update the options for an instance, simple call
|
||||||
|
// 'setOptions' with the new options. Bear in mind, this does NOT merge
|
||||||
|
// existing properties, it's a full replace
|
||||||
|
instance.setOptions({
|
||||||
|
...instance.options,
|
||||||
|
theme: "pierre-dark",
|
||||||
|
themes: undefined,
|
||||||
|
})
|
||||||
|
|
||||||
|
// When ready to render, simply call .render with old/new file, optional
|
||||||
|
// annotations and a container element to hold the diff
|
||||||
|
instance.render({
|
||||||
|
oldFile: props.before,
|
||||||
|
newFile: props.after,
|
||||||
|
lineAnnotations,
|
||||||
|
containerWrapper: container,
|
||||||
|
})
|
||||||
|
|
||||||
|
return <div ref={container} />
|
||||||
|
}
|
||||||
@@ -467,11 +467,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const activeAssistantMessagesWithText = createMemo(() => {
|
|
||||||
if (!store.active || !activeAssistantMessages()) return []
|
|
||||||
return activeAssistantMessages()?.filter((m) => sync.data.part[m.id].find((p) => p.type === "text"))
|
|
||||||
})
|
|
||||||
|
|
||||||
const model = createMemo(() => {
|
const model = createMemo(() => {
|
||||||
if (!last()) return
|
if (!last()) return
|
||||||
const model = sync.data.provider.find((x) => x.id === last().providerID)?.models[last().modelID]
|
const model = sync.data.provider.find((x) => x.id === last().providerID)?.models[last().modelID]
|
||||||
@@ -510,7 +505,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
active,
|
active,
|
||||||
activeMessage,
|
activeMessage,
|
||||||
activeAssistantMessages,
|
activeAssistantMessages,
|
||||||
activeAssistantMessagesWithText,
|
|
||||||
lastUserMessage,
|
lastUserMessage,
|
||||||
cost,
|
cost,
|
||||||
last,
|
last,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Button, List, SelectDialog, Tooltip, IconButton, Tabs, Icon } from "@opencode-ai/ui"
|
import { Button, List, SelectDialog, Tooltip, IconButton, Tabs, Icon } from "@opencode-ai/ui"
|
||||||
import { FileIcon } from "@/ui"
|
import { FileIcon } from "@/ui"
|
||||||
import FileTree from "@/components/file-tree"
|
import FileTree from "@/components/file-tree"
|
||||||
import { For, onCleanup, onMount, Show, Match, Switch, createSignal, createEffect } from "solid-js"
|
import { For, onCleanup, onMount, Show, Match, Switch, createSignal, createEffect, createMemo } from "solid-js"
|
||||||
import { useLocal, type LocalFile, type TextSelection } from "@/context/local"
|
import { useLocal, type LocalFile, type TextSelection } from "@/context/local"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { getDirectory, getFilename } from "@/utils"
|
import { getDirectory, getFilename } from "@/utils"
|
||||||
@@ -21,6 +21,7 @@ import type { JSX } from "solid-js"
|
|||||||
import { Code } from "@/components/code"
|
import { Code } from "@/components/code"
|
||||||
import { useSync } from "@/context/sync"
|
import { useSync } from "@/context/sync"
|
||||||
import { useSDK } from "@/context/sdk"
|
import { useSDK } from "@/context/sdk"
|
||||||
|
import { Diff } from "@/components/diff"
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const local = useLocal()
|
const local = useLocal()
|
||||||
@@ -374,27 +375,36 @@ export default function Page() {
|
|||||||
onSelect={(s) => local.session.setActive(s?.id)}
|
onSelect={(s) => local.session.setActive(s?.id)}
|
||||||
onHover={(s) => (!!s ? sync.session.sync(s?.id) : undefined)}
|
onHover={(s) => (!!s ? sync.session.sync(s?.id) : undefined)}
|
||||||
>
|
>
|
||||||
{(session) => (
|
{(session) => {
|
||||||
<Tooltip placement="right" value={session.title}>
|
const diffs = createMemo(() => session.summary?.diffs ?? [])
|
||||||
<div>
|
const filesChanged = createMemo(() => diffs().length)
|
||||||
<div class="flex items-center self-stretch gap-6">
|
const additions = createMemo(() => diffs().reduce((acc, diff) => (acc ?? 0) + (diff.additions ?? 0), 0))
|
||||||
<span class="text-14-regular text-text-strong overflow-hidden text-ellipsis truncate">
|
const deletions = createMemo(() => diffs().reduce((acc, diff) => (acc ?? 0) + (diff.deletions ?? 0), 0))
|
||||||
{session.title}
|
|
||||||
</span>
|
return (
|
||||||
<span class="text-12-regular text-text-weak text-right whitespace-nowrap">
|
<Tooltip placement="right" value={session.title}>
|
||||||
{DateTime.fromMillis(session.time.updated).toRelative()}
|
<div>
|
||||||
</span>
|
<div class="flex items-center self-stretch gap-6">
|
||||||
</div>
|
<span class="text-14-regular text-text-strong overflow-hidden text-ellipsis truncate">
|
||||||
<div class="flex justify-between items-center self-stretch">
|
{session.title}
|
||||||
<span class="text-12-regular text-text-weak">2 files changed</span>
|
</span>
|
||||||
<div class="flex gap-2 justify-end items-center">
|
<span class="text-12-regular text-text-weak text-right whitespace-nowrap">
|
||||||
<span class="text-12-mono text-right text-text-diff-add-base">+43</span>
|
{DateTime.fromMillis(session.time.updated).toRelative()}
|
||||||
<span class="text-12-mono text-right text-text-diff-delete-base">-2</span>
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center self-stretch">
|
||||||
|
<span class="text-12-regular text-text-weak">{`${filesChanged() || "No"} file${filesChanged() !== 1 ? "s" : ""} changed`}</span>
|
||||||
|
<Show when={additions() || deletions()}>
|
||||||
|
<div class="flex gap-2 justify-end items-center">
|
||||||
|
<span class="text-12-mono text-right text-text-diff-add-base">{`+${additions()}`}</span>
|
||||||
|
<span class="text-12-mono text-right text-text-diff-delete-base">{`-${deletions()}`}</span>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Tooltip>
|
||||||
</Tooltip>
|
)
|
||||||
)}
|
}}
|
||||||
</List>
|
</List>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -521,60 +531,77 @@ export default function Page() {
|
|||||||
{(activeSession) => (
|
{(activeSession) => (
|
||||||
<div class="py-3 flex flex-col flex-1 min-h-0">
|
<div class="py-3 flex flex-col flex-1 min-h-0">
|
||||||
<div class="flex items-start gap-8 flex-1 min-h-0">
|
<div class="flex items-start gap-8 flex-1 min-h-0">
|
||||||
<ul role="list" class="w-60 shrink-0 flex flex-col items-start gap-1">
|
<Show when={local.session.userMessages().length > 1}>
|
||||||
<For each={local.session.userMessages()}>
|
<ul role="list" class="w-60 shrink-0 flex flex-col items-start gap-1">
|
||||||
{(message) => (
|
<For each={local.session.userMessages()}>
|
||||||
<li
|
{(message) => (
|
||||||
class="group/li flex items-center gap-x-2 py-1 self-stretch cursor-default"
|
<li
|
||||||
onClick={() => local.session.setActiveMessage(message.id)}
|
class="group/li flex items-center gap-x-2 py-1 self-stretch cursor-default"
|
||||||
>
|
onClick={() => local.session.setActiveMessage(message.id)}
|
||||||
<div class="w-[18px] shrink-0">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 12" fill="none">
|
|
||||||
<g>
|
|
||||||
<rect x="0" width="2" height="12" rx="1" fill="#CFCECD" />
|
|
||||||
<rect x="4" width="2" height="12" rx="1" fill="#CFCECD" />
|
|
||||||
<rect x="8" width="2" height="12" rx="1" fill="#CFCECD" />
|
|
||||||
<rect x="12" width="2" height="12" rx="1" fill="#CFCECD" />
|
|
||||||
<rect x="16" width="2" height="12" rx="1" fill="#CFCECD" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-active={local.session.activeMessage()?.id === message.id}
|
|
||||||
classList={{
|
|
||||||
"text-14-regular text-text-weak whitespace-nowrap truncate min-w-0": true,
|
|
||||||
"text-text-weak data-[active=true]:text-text-strong group-hover/li:text-text-base": true,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{local.session.getMessageText(message)}
|
<div class="w-[18px] shrink-0">
|
||||||
</div>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 12" fill="none">
|
||||||
</li>
|
<g>
|
||||||
)}
|
<rect x="0" width="2" height="12" rx="1" fill="#CFCECD" />
|
||||||
</For>
|
<rect x="4" width="2" height="12" rx="1" fill="#CFCECD" />
|
||||||
</ul>
|
<rect x="8" width="2" height="12" rx="1" fill="#CFCECD" />
|
||||||
|
<rect x="12" width="2" height="12" rx="1" fill="#CFCECD" />
|
||||||
|
<rect x="16" width="2" height="12" rx="1" fill="#CFCECD" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
data-active={local.session.activeMessage()?.id === message.id}
|
||||||
|
classList={{
|
||||||
|
"text-14-regular text-text-weak whitespace-nowrap truncate min-w-0": true,
|
||||||
|
"text-text-weak data-[active=true]:text-text-strong group-hover/li:text-text-base": true,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{local.session.getMessageText(message)}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</ul>
|
||||||
|
</Show>
|
||||||
<div
|
<div
|
||||||
ref={messageScrollElement}
|
ref={messageScrollElement}
|
||||||
class="grow min-w-0 h-full overflow-y-auto no-scrollbar snap-y"
|
class="grow min-w-0 h-full overflow-y-auto no-scrollbar snap-y"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col items-start gap-50 pb-[800px]">
|
<div class="flex flex-col items-start gap-50 pb-[800px]">
|
||||||
<For each={local.session.userMessages()}>
|
<For each={local.session.userMessages()}>
|
||||||
{(message) => (
|
{(message) => {
|
||||||
<div
|
console.log(message)
|
||||||
data-message={message.id}
|
return (
|
||||||
class="flex flex-col items-start self-stretch gap-8 pt-1.5 snap-start"
|
<div
|
||||||
>
|
data-message={message.id}
|
||||||
<div class="flex flex-col items-start gap-4">
|
class="flex flex-col items-start self-stretch gap-8 pt-1.5 snap-start"
|
||||||
<div class="text-14-medium text-text-strong overflow-hidden text-ellipsis min-w-0">
|
>
|
||||||
{local.session.getMessageText(message)}
|
<div class="flex flex-col items-start gap-4">
|
||||||
|
<div class="text-14-medium text-text-strong overflow-hidden text-ellipsis min-w-0">
|
||||||
|
{local.session.getMessageText(message)}
|
||||||
|
</div>
|
||||||
|
<div class="text-14-regular text-text-base">{message.summary?.text}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-14-regular text-text-base">
|
<div class="">
|
||||||
{message.summary?.text ||
|
<For each={message.summary?.diffs}>
|
||||||
local.session.getMessageText(local.session.activeAssistantMessagesWithText())}
|
{(diff) => (
|
||||||
|
<Diff
|
||||||
|
before={{
|
||||||
|
name: diff.file!,
|
||||||
|
contents: diff.before!,
|
||||||
|
}}
|
||||||
|
after={{
|
||||||
|
name: diff.file!,
|
||||||
|
contents: diff.after!,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class=""></div>
|
)
|
||||||
</div>
|
}}
|
||||||
)}
|
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { $ } from "bun"
|
import { $ } from "bun"
|
||||||
|
import { realpathSync } from "fs"
|
||||||
import os from "os"
|
import os from "os"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ export async function tmpdir<T>(options?: TmpDirOptions<T>) {
|
|||||||
await options?.dispose?.(dirpath)
|
await options?.dispose?.(dirpath)
|
||||||
await $`rm -rf ${dirpath}`.quiet()
|
await $`rm -rf ${dirpath}`.quiet()
|
||||||
},
|
},
|
||||||
path: dirpath,
|
path: realpathSync(dirpath),
|
||||||
extra: extra as T,
|
extra: extra as T,
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
|||||||
46
packages/ui/src/components/collapsible.css
Normal file
46
packages/ui/src/components/collapsible.css
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
[data-component="collapsible"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
[data-slot="trigger"] {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
outline: 2px solid var(--border-focus);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-disabled] {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="content"] {
|
||||||
|
overflow: hidden;
|
||||||
|
/* animation: slideUp 250ms ease-out; */
|
||||||
|
|
||||||
|
/* &[data-expanded] { */
|
||||||
|
/* animation: slideDown 250ms ease-out; */
|
||||||
|
/* } */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideDown {
|
||||||
|
from {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
height: var(--kb-collapsible-content-height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from {
|
||||||
|
height: var(--kb-collapsible-content-height);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
35
packages/ui/src/components/collapsible.tsx
Normal file
35
packages/ui/src/components/collapsible.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { Collapsible as Kobalte, CollapsibleRootProps } from "@kobalte/core/collapsible"
|
||||||
|
import { ComponentProps, ParentProps, splitProps } from "solid-js"
|
||||||
|
|
||||||
|
export interface CollapsibleProps extends ParentProps<CollapsibleRootProps> {
|
||||||
|
class?: string
|
||||||
|
classList?: ComponentProps<"div">["classList"]
|
||||||
|
}
|
||||||
|
|
||||||
|
function CollapsibleRoot(props: CollapsibleProps) {
|
||||||
|
const [local, others] = splitProps(props, ["class", "classList"])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Kobalte
|
||||||
|
data-component="collapsible"
|
||||||
|
classList={{
|
||||||
|
...(local.classList ?? {}),
|
||||||
|
[local.class ?? ""]: !!local.class,
|
||||||
|
}}
|
||||||
|
{...others}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CollapsibleTrigger(props: ComponentProps<typeof Kobalte.Trigger>) {
|
||||||
|
return <Kobalte.Trigger data-slot="trigger" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function CollapsibleContent(props: ComponentProps<typeof Kobalte.Content>) {
|
||||||
|
return <Kobalte.Content data-slot="content" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Collapsible = Object.assign(CollapsibleRoot, {
|
||||||
|
Trigger: CollapsibleTrigger,
|
||||||
|
Content: CollapsibleContent,
|
||||||
|
})
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from "./button"
|
export * from "./button"
|
||||||
|
export * from "./collapsible"
|
||||||
export * from "./dialog"
|
export * from "./dialog"
|
||||||
export * from "./icon"
|
export * from "./icon"
|
||||||
export * from "./icon-button"
|
export * from "./icon-button"
|
||||||
|
|||||||
@@ -1,6 +1,19 @@
|
|||||||
import type { Component } from "solid-js"
|
import type { Component } from "solid-js"
|
||||||
import { createSignal } from "solid-js"
|
import { createSignal } from "solid-js"
|
||||||
import { Button, Select, Tabs, Tooltip, Fonts, List, Dialog, Icon, IconButton, Input, SelectDialog } from "./components"
|
import {
|
||||||
|
Button,
|
||||||
|
Select,
|
||||||
|
Tabs,
|
||||||
|
Tooltip,
|
||||||
|
Fonts,
|
||||||
|
List,
|
||||||
|
Dialog,
|
||||||
|
Icon,
|
||||||
|
IconButton,
|
||||||
|
Input,
|
||||||
|
SelectDialog,
|
||||||
|
Collapsible,
|
||||||
|
} from "./components"
|
||||||
import "./index.css"
|
import "./index.css"
|
||||||
|
|
||||||
const Demo: Component = () => {
|
const Demo: Component = () => {
|
||||||
@@ -180,6 +193,27 @@ const Demo: Component = () => {
|
|||||||
{(item) => <div>{item}</div>}
|
{(item) => <div>{item}</div>}
|
||||||
</SelectDialog>
|
</SelectDialog>
|
||||||
</section>
|
</section>
|
||||||
|
<h3>Collapsible</h3>
|
||||||
|
<section>
|
||||||
|
<Collapsible>
|
||||||
|
<Collapsible.Trigger>
|
||||||
|
<Button variant="secondary">Toggle Content</Button>
|
||||||
|
</Collapsible.Trigger>
|
||||||
|
<Collapsible.Content>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: "16px",
|
||||||
|
"background-color": "var(--surface-base)",
|
||||||
|
"border-radius": "8px",
|
||||||
|
"margin-top": "8px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p>This is collapsible content that can be toggled open and closed.</p>
|
||||||
|
<p>It animates smoothly using CSS animations.</p>
|
||||||
|
</div>
|
||||||
|
</Collapsible.Content>
|
||||||
|
</Collapsible>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user