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