From 37e6c8342f1f63a95918a71ac78130b9d10d1103 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Tue, 14 Oct 2025 07:14:26 -0500 Subject: wip: css and ui packages --- packages/ui/src/components/button.tsx | 25 ++++++ packages/ui/src/components/icon.tsx | 155 ++++++++++++++++++++++++++++++++++ packages/ui/src/components/index.ts | 3 + packages/ui/src/components/select.tsx | 92 ++++++++++++++++++++ packages/ui/src/components/style.css | 2 + 5 files changed, 277 insertions(+) create mode 100644 packages/ui/src/components/button.tsx create mode 100644 packages/ui/src/components/icon.tsx create mode 100644 packages/ui/src/components/index.ts create mode 100644 packages/ui/src/components/select.tsx create mode 100644 packages/ui/src/components/style.css (limited to 'packages/ui/src/components') diff --git a/packages/ui/src/components/button.tsx b/packages/ui/src/components/button.tsx new file mode 100644 index 000000000..db1da2fb9 --- /dev/null +++ b/packages/ui/src/components/button.tsx @@ -0,0 +1,25 @@ +import { Button as Kobalte } from "@kobalte/core/button" +import { type ComponentProps, splitProps } from "solid-js" + +export interface ButtonProps { + size?: "normal" | "large" + variant?: "primary" | "secondary" | "ghost" +} + +export function Button(props: ComponentProps<"button"> & ButtonProps) { + const [split, rest] = splitProps(props, ["variant", "size", "class", "classList"]) + return ( + + {props.children} + + ) +} diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx new file mode 100644 index 000000000..05dda6ea6 --- /dev/null +++ b/packages/ui/src/components/icon.tsx @@ -0,0 +1,155 @@ +import { splitProps, type ComponentProps } from "solid-js" + +// prettier-ignore +const icons = { + close: '', + menu: ' ', + "chevron-right": '', + "chevron-left": '', + "chevron-down": '', + "chevron-up": '', + "chevron-down-square": '', + "chevron-up-square": '', + "chevron-right-square": '', + "chevron-left-square": '', + settings: '', + globe: '', + github: '', + hammer: '', + "avatar-square": '', + slash: '', + robot: '', + cloud: '', + "file-text": '', + file: '', + "file-checkmark": '', + "file-code": '', + "file-important": '', + "file-minus": '', + "file-plus": '', + files: '', + "file-zip": '', + jpg: '', + pdf: '', + png: '', + gif: '', + archive: '', + sun: '', + moon: '', + monitor: '', + command: '', + link: '', + share: '', + branch: '', + logout: '', + login: '', + keys: '', + key: '', + info: '', + warning: '', + checkmark: '', + "checkmark-square": '', + plus: '', + minus: '', + undo: '', + merge: '', + redo: '', + refresh: '', + rotate: '', + "arrow-left": '', + "arrow-down": '', + "arrow-right": '', + "arrow-up": '', + enter: '', + trash: '', + package: '', + box: '', + lock: '', + unlocked: '', + activity: '', + asterisk: '', + bell: '', + "bell-off": '', + bolt: '', + bookmark: '', + brain: '', + browser: '', + "browser-cursor": '', + bug: '', + "carat-down": '', + "carat-left": '', + "carat-right": '', + "carat-up": '', + cards: '', + chart: '', + "check-circle": '', + checklist: '', + "checklist-cards": '', + lab: '', + circle: '', + "circle-dotted": '', + clipboard: '', + clock: '', + "close-circle": '', + terminal: '', + code: '', + components: '', + copy: '', + cpu: '', + dashboard: '', + transfer: '', + devices: '', + diamond: '', + dice: '', + discord: '', + dots: '', + expand: '', + droplet: '', + "chevron-double-down": '', + "chevron-double-left": '', + "chevron-double-right": '', + "chevron-double-up": '', + "speech-bubble": '', + message: '', + annotation: '', + square: '', + "pull-request": '', + pencil: '', + sparkles: '', + photo: '', + columns: '', + "open-pane": '', + "close-pane": '', + "file-search": '', + "folder-search": '', + search: '', + "web-search": '', + loading: '', + mic: '', +} as const + +export interface IconProps extends ComponentProps<"svg"> { + name: keyof typeof icons + size?: number +} + +export function Icon(props: IconProps) { + const [local, others] = splitProps(props, ["name", "size", "class", "classList"]) + const size = local.size ?? 24 + return ( + + ) +} diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts new file mode 100644 index 000000000..80d80299b --- /dev/null +++ b/packages/ui/src/components/index.ts @@ -0,0 +1,3 @@ +export * from "./button" +export * from "./icon" +export * from "./select" diff --git a/packages/ui/src/components/select.tsx b/packages/ui/src/components/select.tsx new file mode 100644 index 000000000..c4d5443c1 --- /dev/null +++ b/packages/ui/src/components/select.tsx @@ -0,0 +1,92 @@ +import { Select as Kobalte } from "@kobalte/core/select" +import { createMemo, type ComponentProps } from "solid-js" +import { Icon, Button, type ButtonProps } from "@opencode-ai/ui" +import { pipe, groupBy, entries, map } from "remeda" + +export interface SelectProps { + placeholder?: string + options: T[] + current?: T + value?: (x: T) => string + label?: (x: T) => string + groupBy?: (x: T) => string + onSelect?: (value: T | undefined) => void + class?: ComponentProps<"div">["class"] + classList?: ComponentProps<"div">["classList"] +} + +export function Select(props: SelectProps & ButtonProps) { + const grouped = createMemo(() => { + const result = pipe( + props.options, + groupBy((x) => (props.groupBy ? props.groupBy(x) : "")), + // mapValues((x) => x.sort((a, b) => a.title.localeCompare(b.title))), + entries(), + map(([k, v]) => ({ category: k, options: v })), + ) + return result + }) + + return ( + + data-component="select" + value={props.current} + options={grouped()} + optionValue={(x) => (props.value ? props.value(x) : (x as string))} + optionTextValue={(x) => (props.label ? props.label(x) : (x as string))} + optionGroupChildren="options" + placeholder={props.placeholder} + sectionComponent={(props) => ( + {props.section.rawValue.category} + )} + itemComponent={(itemProps) => ( + + + {props.label ? props.label(itemProps.item.rawValue) : (itemProps.item.rawValue as string)} + + + + + + )} + onChange={(v) => { + props.onSelect?.(v ?? undefined) + }} + > + + data-slot="value"> + {(state) => { + const selected = state.selectedOption() ?? props.current + if (!selected) return props.placeholder || "" + if (props.label) return props.label(selected) + return selected as string + }} + + + + + + + + + + + + ) +} diff --git a/packages/ui/src/components/style.css b/packages/ui/src/components/style.css new file mode 100644 index 000000000..52539dc8f --- /dev/null +++ b/packages/ui/src/components/style.css @@ -0,0 +1,2 @@ +/* re-exporting for convenience */ +@import "@opencode-ai/css"; -- cgit v1.2.3