Extract shared UI and theme packages

This commit is contained in:
Gregory Schier
2026-03-06 10:30:31 -08:00
parent 6915778c06
commit fd100330a6
33 changed files with 1388 additions and 1021 deletions

View File

@@ -1,48 +1,8 @@
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
export type Appearance = 'light' | 'dark';
export function getCSSAppearance(): Appearance {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
export async function getWindowAppearance(): Promise<Appearance> {
const a = await getCurrentWebviewWindow().theme();
return a ?? getCSSAppearance();
}
/**
* Subscribe to appearance (dark/light) changes. Note, we use Tauri Window appearance instead of
* CSS appearance because CSS won't fire the way we handle window theme management.
*/
export function subscribeToWindowAppearanceChange(
cb: (appearance: Appearance) => void,
): () => void {
const container = {
unsubscribe: () => {},
};
getCurrentWebviewWindow()
.onThemeChanged((t) => {
cb(t.payload);
})
.then((l) => {
container.unsubscribe = l;
});
return () => container.unsubscribe();
}
export function resolveAppearance(
preferredAppearance: Appearance,
appearanceSetting: string,
): Appearance {
const appearance = appearanceSetting === 'system' ? preferredAppearance : appearanceSetting;
return appearance === 'dark' ? 'dark' : 'light';
}
export function subscribeToPreferredAppearance(cb: (a: Appearance) => void) {
cb(getCSSAppearance());
getWindowAppearance().then(cb);
subscribeToWindowAppearanceChange(cb);
}
export type { Appearance } from "@yaakapp-internal/theme";
export {
getCSSAppearance,
getWindowAppearance,
resolveAppearance,
subscribeToPreferredAppearance,
subscribeToWindowAppearanceChange,
} from "@yaakapp-internal/theme";

View File

@@ -1,14 +1,21 @@
import type { GetThemesResponse } from '@yaakapp-internal/plugins';
import { invokeCmd } from '../tauri';
import type { Appearance } from './appearance';
import { resolveAppearance } from './appearance';
import type { GetThemesResponse } from "@yaakapp-internal/plugins";
import { defaultDarkTheme, defaultLightTheme } from "@yaakapp-internal/theme";
import { invokeCmd } from "../tauri";
import type { Appearance } from "./appearance";
import { resolveAppearance } from "./appearance";
export { defaultDarkTheme, defaultLightTheme } from "@yaakapp-internal/theme";
export async function getThemes() {
const themes = (await invokeCmd<GetThemesResponse[]>('cmd_get_themes')).flatMap((t) => t.themes);
const themes = (
await invokeCmd<GetThemesResponse[]>("cmd_get_themes")
).flatMap((t) => t.themes);
themes.sort((a, b) => a.label.localeCompare(b.label));
// Remove duplicates, in case multiple plugins provide the same theme
const uniqueThemes = Array.from(new Map(themes.map((t) => [t.id, t])).values());
return { themes: [yaakDark, yaakLight, ...uniqueThemes] };
const uniqueThemes = Array.from(
new Map(themes.map((t) => [t.id, t])).values(),
);
return { themes: [defaultDarkTheme, defaultLightTheme, ...uniqueThemes] };
}
export async function getResolvedTheme(
@@ -23,88 +30,16 @@ export async function getResolvedTheme(
const darkThemes = themes.filter((t) => t.dark);
const lightThemes = themes.filter((t) => !t.dark);
const dark = darkThemes.find((t) => t.id === themeDark) ?? darkThemes[0] ?? yaakDark;
const light = lightThemes.find((t) => t.id === themeLight) ?? lightThemes[0] ?? yaakLight;
const dark =
darkThemes.find((t) => t.id === themeDark) ??
darkThemes[0] ??
defaultDarkTheme;
const light =
lightThemes.find((t) => t.id === themeLight) ??
lightThemes[0] ??
defaultLightTheme;
const active = appearance === 'dark' ? dark : light;
const active = appearance === "dark" ? dark : light;
return { dark, light, active };
}
const yaakDark = {
id: 'yaak-dark',
label: 'Yaak',
dark: true,
base: {
surface: 'hsl(244,23%,14%)',
surfaceHighlight: 'hsl(244,23%,20%)',
text: 'hsl(245,23%,85%)',
textSubtle: 'hsl(245,18%,58%)',
textSubtlest: 'hsl(245,18%,45%)',
border: 'hsl(244,23%,25%)',
primary: 'hsl(266,100%,79%)',
secondary: 'hsl(245,23%,60%)',
info: 'hsl(206,100%,63%)',
success: 'hsl(150,99%,44%)',
notice: 'hsl(48,80%,63%)',
warning: 'hsl(28,100%,61%)',
danger: 'hsl(342,90%,68%)',
},
components: {
button: {
primary: 'hsl(266,100%,71.1%)',
secondary: 'hsl(244,23%,54%)',
info: 'hsl(206,100%,56.7%)',
success: 'hsl(150,99%,37.4%)',
notice: 'hsl(48,80%,50.4%)',
warning: 'hsl(28,100%,54.9%)',
danger: 'hsl(342,90%,61.2%)',
},
dialog: {
border: 'hsl(244,23%,24%)',
},
sidebar: {
surface: 'hsl(243,23%,16%)',
border: 'hsl(244,23%,22%)',
},
responsePane: {
surface: 'hsl(243,23%,16%)',
border: 'hsl(246,23%,23%)',
},
appHeader: {
surface: 'hsl(244,23%,12%)',
border: 'hsl(244,23%,21%)',
},
},
};
const yaakLight = {
id: 'yaak-light',
label: 'Yaak',
dark: false,
base: {
surface: 'hsl(0,0%,100%)',
surfaceHighlight: 'hsl(218,24%,87%)',
text: 'hsl(217,24%,10%)',
textSubtle: 'hsl(217,24%,40%)',
textSubtlest: 'hsl(217,24%,58%)',
border: 'hsl(217,22%,90%)',
primary: 'hsl(266,100%,60%)',
secondary: 'hsl(220,24%,50%)',
info: 'hsl(206,100%,40%)',
success: 'hsl(139,66%,34%)',
notice: 'hsl(45,100%,34%)',
warning: 'hsl(30,100%,36%)',
danger: 'hsl(335,75%,48%)',
},
components: {
sidebar: {
surface: 'hsl(220,20%,98%)',
border: 'hsl(217,22%,88%)',
surfaceHighlight: 'hsl(217,25%,90%)',
},
},
};
export const defaultDarkTheme = yaakDark;
export const defaultLightTheme = yaakLight;

View File

@@ -1,386 +1,13 @@
import type { Theme, ThemeComponentColors } from '@yaakapp-internal/plugins';
import { defaultDarkTheme, defaultLightTheme } from './themes';
import { YaakColor } from './yaakColor';
export type YaakColors = {
surface: YaakColor;
surfaceHighlight?: YaakColor;
surfaceActive?: YaakColor;
text: YaakColor;
textSubtle?: YaakColor;
textSubtlest?: YaakColor;
border?: YaakColor;
borderSubtle?: YaakColor;
borderFocus?: YaakColor;
shadow?: YaakColor;
backdrop?: YaakColor;
selection?: YaakColor;
primary?: YaakColor;
secondary?: YaakColor;
info?: YaakColor;
success?: YaakColor;
notice?: YaakColor;
warning?: YaakColor;
danger?: YaakColor;
};
export type YaakTheme = {
id: string;
name: string;
base: YaakColors;
components?: Partial<{
dialog: Partial<YaakColors>;
menu: Partial<YaakColors>;
toast: Partial<YaakColors>;
sidebar: Partial<YaakColors>;
responsePane: Partial<YaakColors>;
appHeader: Partial<YaakColors>;
button: Partial<YaakColors>;
banner: Partial<YaakColors>;
templateTag: Partial<YaakColors>;
urlBar: Partial<YaakColors>;
editor: Partial<YaakColors>;
input: Partial<YaakColors>;
}>;
};
export type YaakColorKey = keyof ThemeComponentColors;
type ComponentName = keyof NonNullable<YaakTheme['components']>;
type CSSVariables = Record<YaakColorKey, string | undefined>;
function themeVariables(
theme: Theme,
component?: ComponentName,
base?: CSSVariables,
): CSSVariables | null {
const cmp =
component == null
? theme.base
: (theme.components?.[component] ?? ({} as ThemeComponentColors));
const c = (s: string | undefined) => yc(theme, s);
const vars: CSSVariables = {
surface: cmp.surface,
surfaceHighlight: cmp.surfaceHighlight ?? c(cmp.surface)?.lift(0.06).css(),
surfaceActive: cmp.surfaceActive ?? c(cmp.primary)?.lower(0.2).translucify(0.8).css(),
backdrop: cmp.backdrop ?? c(cmp.surface)?.lower(0.2).translucify(0.2).css(),
selection: cmp.selection ?? c(cmp.primary)?.lower(0.1).translucify(0.7).css(),
border: cmp.border ?? c(cmp.surface)?.lift(0.11)?.css(),
borderSubtle: cmp.borderSubtle ?? c(cmp.border)?.lower(0.06)?.css(),
borderFocus: c(cmp.info)?.translucify(0.5)?.css(),
text: cmp.text,
textSubtle: cmp.textSubtle ?? c(cmp.text)?.lower(0.2)?.css(),
textSubtlest: cmp.textSubtlest ?? c(cmp.text)?.lower(0.3)?.css(),
shadow:
cmp.shadow ??
YaakColor.black()
.translucify(theme.dark ? 0.7 : 0.93)
.css(),
primary: cmp.primary,
secondary: cmp.secondary,
info: cmp.info,
success: cmp.success,
notice: cmp.notice,
warning: cmp.warning,
danger: cmp.danger,
};
// Extend with base
for (const [k, v] of Object.entries(vars)) {
if (!v && base?.[k as YaakColorKey]) {
vars[k as YaakColorKey] = base[k as YaakColorKey];
}
}
return vars;
}
function templateTagColorVariables(color: YaakColor | null): Partial<CSSVariables> {
if (color == null) return {};
return {
text: color.lift(0.7).css(),
textSubtle: color.lift(0.4).css(),
textSubtlest: color.css(),
surface: color.lower(0.2).translucify(0.8).css(),
border: color.translucify(0.6).css(),
borderSubtle: color.translucify(0.8).css(),
surfaceHighlight: color.lower(0.1).translucify(0.7).css(),
};
}
function toastColorVariables(color: YaakColor | null): Partial<CSSVariables> {
if (color == null) return {};
return {
text: color.lift(0.8).css(),
textSubtle: color.lift(0.8).translucify(0.3).css(),
surface: color.translucify(0.9).css(),
surfaceHighlight: color.translucify(0.8).css(),
border: color.lift(0.3).translucify(0.6).css(),
};
}
function bannerColorVariables(color: YaakColor | null): Partial<CSSVariables> {
if (color == null) return {};
return {
text: color.lift(0.8).css(),
textSubtle: color.translucify(0.3).css(),
textSubtlest: color.translucify(0.6).css(),
surface: color.translucify(0.95).css(),
border: color.lift(0.3).translucify(0.8).css(),
};
}
function inputCSS(color: YaakColor | null): Partial<CSSVariables> {
if (color == null) return {};
const theme: Partial<ThemeComponentColors> = {
border: color.css(),
};
return theme;
}
function buttonSolidColorVariables(
color: YaakColor | null,
isDefault = false,
): Partial<CSSVariables> {
if (color == null) return {};
const theme: Partial<ThemeComponentColors> = {
text: 'white',
surface: color.lower(0.3).css(),
surfaceHighlight: color.lower(0.1).css(),
border: color.css(),
};
if (isDefault) {
theme.text = undefined; // Inherit from parent
theme.surface = undefined; // Inherit from parent
theme.surfaceHighlight = color.lift(0.08).css();
}
return theme;
}
function buttonBorderColorVariables(
color: YaakColor | null,
isDefault = false,
): Partial<CSSVariables> {
if (color == null) return {};
const vars: Partial<CSSVariables> = {
text: color.lift(0.8).css(),
textSubtle: color.lift(0.55).css(),
textSubtlest: color.lift(0.4).translucify(0.6).css(),
surfaceHighlight: color.translucify(0.8).css(),
borderSubtle: color.translucify(0.5).css(),
border: color.translucify(0.3).css(),
};
if (isDefault) {
vars.borderSubtle = color.lift(0.28).css();
vars.border = color.lift(0.5).css();
}
return vars;
}
function variablesToCSS(
selector: string | null,
vars: Partial<CSSVariables> | null,
): string | null {
if (vars == null) {
return null;
}
const css = Object.entries(vars ?? {})
.filter(([, value]) => value)
.map(([name, value]) => `--${name}: ${value};`)
.join('\n');
return selector == null ? css : `${selector} {\n${indent(css)}\n}`;
}
function componentCSS(theme: Theme, component: ComponentName): string | null {
if (theme.components == null) {
return null;
}
const themeVars = themeVariables(theme, component);
return variablesToCSS(`.x-theme-${component}`, themeVars);
}
function buttonCSS(
theme: Theme,
color: YaakColorKey,
colors?: ThemeComponentColors,
): string | null {
const yaakColor = yc(theme, colors?.[color]);
if (yaakColor == null) {
return null;
}
return [
variablesToCSS(`.x-theme-button--solid--${color}`, buttonSolidColorVariables(yaakColor)),
variablesToCSS(`.x-theme-button--border--${color}`, buttonBorderColorVariables(yaakColor)),
].join('\n\n');
}
function bannerCSS(
theme: Theme,
color: YaakColorKey,
colors?: ThemeComponentColors,
): string | null {
const yaakColor = yc(theme, colors?.[color]);
if (yaakColor == null) {
return null;
}
return [variablesToCSS(`.x-theme-banner--${color}`, bannerColorVariables(yaakColor))].join(
'\n\n',
);
}
function toastCSS(theme: Theme, color: YaakColorKey, colors?: ThemeComponentColors): string | null {
const yaakColor = yc(theme, colors?.[color]);
if (yaakColor == null) {
return null;
}
return [variablesToCSS(`.x-theme-toast--${color}`, toastColorVariables(yaakColor))].join('\n\n');
}
function templateTagCSS(
theme: Theme,
color: YaakColorKey,
colors?: ThemeComponentColors,
): string | null {
const yaakColor = yc(theme, colors?.[color]);
if (yaakColor == null) {
return null;
}
return [
variablesToCSS(`.x-theme-templateTag--${color}`, templateTagColorVariables(yaakColor)),
].join('\n\n');
}
export function getThemeCSS(theme: Theme): string {
theme.components = theme.components ?? {};
// Toast defaults to menu styles
theme.components.toast = theme.components.toast ?? theme.components.menu ?? {};
const { components, id, label } = theme;
const colors = Object.keys(theme.base).reduce((prev, key) => {
// biome-ignore lint/performance/noAccumulatingSpread: none
return { ...prev, [key]: theme.base[key as YaakColorKey] };
}, {}) as ThemeComponentColors;
let themeCSS = '';
try {
const baseCss = variablesToCSS(null, themeVariables(theme));
themeCSS = [
baseCss,
...Object.keys(components ?? {}).map((key) => componentCSS(theme, key as ComponentName)),
variablesToCSS(
'.x-theme-button--solid--default',
buttonSolidColorVariables(yc(theme, theme.base.surface), true),
),
variablesToCSS(
'.x-theme-button--border--default',
buttonBorderColorVariables(yc(theme, theme.base.surface), true),
),
...Object.keys(colors ?? {}).map((key) =>
buttonCSS(theme, key as YaakColorKey, theme.components?.button ?? colors),
),
...Object.keys(colors ?? {}).map((key) =>
bannerCSS(theme, key as YaakColorKey, theme.components?.banner ?? colors),
),
...Object.keys(colors ?? {}).map((key) =>
toastCSS(theme, key as YaakColorKey, theme.components?.banner ?? colors),
),
...Object.keys(colors ?? {}).map((key) =>
templateTagCSS(theme, key as YaakColorKey, theme.components?.templateTag ?? colors),
),
].join('\n\n');
} catch (err) {
console.error('Failed to generate CSS', err);
}
return [`/* ${label} */`, `[data-theme="${id}"] {`, indent(themeCSS), '}'].join('\n');
}
export function addThemeStylesToDocument(rawTheme: Theme | null) {
if (rawTheme == null) {
console.error('Failed to add theme styles: theme is null');
return;
}
const theme = completeTheme(rawTheme);
let styleEl = document.head.querySelector('style[data-theme]');
if (!styleEl) {
styleEl = document.createElement('style');
document.head.appendChild(styleEl);
}
styleEl.setAttribute('data-theme', theme.id);
styleEl.setAttribute('data-updated-at', new Date().toISOString());
styleEl.textContent = getThemeCSS(theme);
}
export function setThemeOnDocument(theme: Theme | null) {
if (theme == null) {
console.error('Failed to set theme: theme is null');
return;
}
document.documentElement.setAttribute('data-theme', theme.id);
}
export function indent(text: string, space = ' '): string {
return text
.split('\n')
.map((line) => space + line)
.join('\n');
}
function yc<T extends string | null | undefined>(
theme: Theme,
s: T,
): T extends string ? YaakColor : null {
if (s == null) return null as never;
return new YaakColor(s, theme.dark ? 'dark' : 'light') as never;
}
export function completeTheme(theme: Theme): Theme {
const fallback = theme.dark ? defaultDarkTheme.base : defaultLightTheme.base;
const c = (s: string | null | undefined) => yc(theme, s);
theme.base.primary ??= fallback.primary;
theme.base.secondary ??= fallback.secondary;
theme.base.info ??= fallback.info;
theme.base.success ??= fallback.success;
theme.base.notice ??= fallback.notice;
theme.base.warning ??= fallback.warning;
theme.base.danger ??= fallback.danger;
theme.base.surface ??= fallback.surface;
theme.base.surfaceHighlight ??= c(theme.base.surface)?.lift(0.06)?.css();
theme.base.surfaceActive ??= c(theme.base.primary)?.lower(0.2).translucify(0.8).css();
theme.base.border ??= c(theme.base.surface)?.lift(0.12)?.css();
theme.base.borderSubtle ??= c(theme.base.border)?.lower(0.08)?.css();
theme.base.text ??= fallback.text;
theme.base.textSubtle ??= c(theme.base.text)?.lower(0.3)?.css();
theme.base.textSubtlest ??= c(theme.base.text)?.lower(0.5)?.css();
return theme;
}
export type {
YaakColorKey,
YaakColors,
YaakTheme,
} from "@yaakapp-internal/theme";
export {
addThemeStylesToDocument,
applyThemeToDocument,
completeTheme,
getThemeCSS,
indent,
setThemeOnDocument,
} from "@yaakapp-internal/theme";

View File

@@ -1,158 +1 @@
import parseColor from 'parse-color';
export class YaakColor {
private readonly appearance: 'dark' | 'light' = 'light';
private hue = 0;
private saturation = 0;
private lightness = 0;
private alpha = 1;
constructor(cssColor: string, appearance: 'dark' | 'light' = 'light') {
try {
this.set(cssColor);
this.appearance = appearance;
} catch (err) {
console.log('Failed to parse CSS color', cssColor, err);
}
}
static transparent(): YaakColor {
return new YaakColor('rgb(0,0,0)', 'light').translucify(1);
}
static white(): YaakColor {
return new YaakColor('rgb(0,0,0)', 'light').lower(1);
}
static black(): YaakColor {
return new YaakColor('rgb(0,0,0)', 'light').lift(1);
}
set(cssColor: string): YaakColor {
let fixedCssColor = cssColor;
if (cssColor.startsWith('#') && cssColor.length === 9) {
const [r, g, b, a] = hexToRgba(cssColor);
fixedCssColor = `rgba(${r},${g},${b},${a})`;
}
const { hsla } = parseColor(fixedCssColor);
this.hue = hsla[0];
this.saturation = hsla[1];
this.lightness = hsla[2];
this.alpha = hsla[3] ?? 1;
return this;
}
clone(): YaakColor {
return new YaakColor(this.css(), this.appearance);
}
lower(mod: number): YaakColor {
return this.appearance === 'dark' ? this._darken(mod) : this._lighten(mod);
}
lift(mod: number): YaakColor {
return this.appearance === 'dark' ? this._lighten(mod) : this._darken(mod);
}
minLightness(n: number): YaakColor {
const c = this.clone();
if (c.lightness < n) {
c.lightness = n;
}
return c;
}
isDark(): boolean {
return this.lightness < 50;
}
translucify(mod: number): YaakColor {
const c = this.clone();
c.alpha = c.alpha - c.alpha * mod;
return c;
}
opacify(mod: number): YaakColor {
const c = this.clone();
c.alpha = this.alpha + (100 - this.alpha) * mod;
return c;
}
desaturate(mod: number): YaakColor {
const c = this.clone();
c.saturation = c.saturation - c.saturation * mod;
return c;
}
saturate(mod: number): YaakColor {
const c = this.clone();
c.saturation = this.saturation + (100 - this.saturation) * mod;
return c;
}
lighterThan(c: YaakColor): boolean {
return this.lightness > c.lightness;
}
css(): string {
const h = this.hue;
const s = this.saturation;
const l = this.lightness;
const a = this.alpha;
const [r, g, b] = parseColor(`hsl(${h},${s}%,${l}%)`).rgb;
return rgbaToHex(r, g, b, a);
}
hexNoAlpha(): string {
const h = this.hue;
const s = this.saturation;
const l = this.lightness;
const [r, g, b] = parseColor(`hsl(${h},${s}%,${l}%)`).rgb;
return rgbaToHexNoAlpha(r, g, b);
}
private _lighten(mod: number): YaakColor {
const c = this.clone();
c.lightness = this.lightness + (100 - this.lightness) * mod;
return c;
}
private _darken(mod: number): YaakColor {
const c = this.clone();
c.lightness = this.lightness - this.lightness * mod;
return c;
}
}
function rgbaToHex(r: number, g: number, b: number, a: number): string {
const toHex = (n: number): string => {
const hex = Number(Math.round(n)).toString(16);
return hex.length === 1 ? `0${hex}` : hex;
};
return `#${[toHex(r), toHex(g), toHex(b), toHex(a * 255)].join('').toUpperCase()}`;
}
function rgbaToHexNoAlpha(r: number, g: number, b: number): string {
const toHex = (n: number): string => {
const hex = Number(Math.round(n)).toString(16);
return hex.length === 1 ? `0${hex}` : hex;
};
return `#${[toHex(r), toHex(g), toHex(b)].join('').toUpperCase()}`;
}
function hexToRgba(hex: string): [number, number, number, number] {
const fromHex = (h: string): number => {
if (h === '') return 255;
return Number(`0x${h}`);
};
const r = fromHex(hex.slice(1, 3));
const g = fromHex(hex.slice(3, 5));
const b = fromHex(hex.slice(5, 7));
const a = fromHex(hex.slice(7, 9));
return [r, g, b, a / 255];
}
export { YaakColor } from "@yaakapp-internal/theme";

View File

@@ -1,29 +1,33 @@
import './main.css';
import { RouterProvider } from '@tanstack/react-router';
import { type } from '@tauri-apps/plugin-os';
import { changeModelStoreWorkspace, initModelStore } from '@yaakapp-internal/models';
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { initSync } from './init/sync';
import { initGlobalListeners } from './lib/initGlobalListeners';
import { jotaiStore } from './lib/jotai';
import { router } from './lib/router';
import "./main.css";
import { RouterProvider } from "@tanstack/react-router";
import { type } from "@tauri-apps/plugin-os";
import {
changeModelStoreWorkspace,
initModelStore,
} from "@yaakapp-internal/models";
import { setPlatformOnDocument } from "@yaakapp-internal/theme";
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { initSync } from "./init/sync";
import { initGlobalListeners } from "./lib/initGlobalListeners";
import { jotaiStore } from "./lib/jotai";
import { router } from "./lib/router";
const osType = type();
document.documentElement.setAttribute('data-platform', osType);
setPlatformOnDocument(osType);
window.addEventListener('keydown', (e) => {
window.addEventListener("keydown", (e) => {
const rx = /input|select|textarea/i;
const target = e.target;
if (e.key !== 'Backspace') return;
if (e.key !== "Backspace") return;
if (!(target instanceof Element)) return;
if (target.getAttribute('contenteditable') !== null) return;
if (target.getAttribute("contenteditable") !== null) return;
if (
!rx.test(target.tagName) ||
('disabled' in target && target.disabled) ||
('readOnly' in target && target.readOnly)
("disabled" in target && target.disabled) ||
("readOnly" in target && target.readOnly)
) {
e.preventDefault();
}
@@ -35,8 +39,8 @@ initModelStore(jotaiStore);
initGlobalListeners();
await changeModelStoreWorkspace(null); // Load global models
console.log('Creating React root');
createRoot(document.getElementById('root') as HTMLElement).render(
console.log("Creating React root");
createRoot(document.getElementById("root") as HTMLElement).render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>,

View File

@@ -87,6 +87,8 @@
"@types/react-syntax-highlighter": "^15.5.13",
"@types/uuid": "^10.0.0",
"@types/whatwg-mimetype": "^3.0.2",
"@yaakapp-internal/theme": "^1.0.0",
"@yaakapp-internal/ui": "^1.0.0",
"@vitejs/plugin-react": "^4.6.0",
"autoprefixer": "^10.4.21",
"decompress": "^4.2.1",

View File

@@ -1,146 +1,16 @@
const plugin = require('tailwindcss/plugin');
const sizes = {
'2xs': '1.4rem',
xs: '1.8rem',
sm: '2.0rem',
md: '2.3rem',
lg: '2.6rem',
};
const sharedConfig = require("@yaakapp-internal/tailwind-config");
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ['class', '[data-resolved-appearance="dark"]'],
...sharedConfig,
content: [
'./*.{html,ts,tsx}',
'./commands/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./hooks/**/*.{ts,tsx}',
'./init/**/*.{ts,tsx}',
'./lib/**/*.{ts,tsx}',
'./routes/**/*.{ts,tsx}',
],
theme: {
extend: {
keyframes: {
blinkRing: {
'0%, 49%': { '--tw-ring-color': 'var(--primary)' },
'50%, 99%': { '--tw-ring-color': 'transparent' },
'100%': { '--tw-ring-color': 'var(--primary)' },
},
},
animation: {
blinkRing: 'blinkRing 150ms step-start 400ms infinite',
},
opacity: {
disabled: '0.3',
},
fontSize: {
xs: '0.8rem',
},
height: sizes,
width: sizes,
minHeight: sizes,
minWidth: sizes,
lineHeight: {
// HACK: Minus 2 to account for borders inside inputs
xs: 'calc(1.75rem - 2px)',
sm: 'calc(2.0rem - 2px)',
md: 'calc(2.5rem - 2px)',
},
transitionProperty: {
grid: 'grid',
},
},
fontFamily: {
mono: [
'var(--font-family-editor)',
'JetBrains Mono',
'ui-monospace',
'SFMono-Regular',
'Menlo',
'Monaco',
'Fira Code',
'Ubuntu Mono',
'Consolas',
'Liberation Mono',
'Courier New',
'DejaVu Sans Mono',
'Hack',
'monospace',
],
sans: [
'var(--font-family-interface)',
'Inter UI',
'-apple-system',
'BlinkMacSystemFont',
'Segoe UI',
'Roboto',
'Oxygen-Sans',
'Ubuntu',
'Cantarell',
'Helvetica Neue',
'sans-serif',
'Apple Color Emoji',
'Segoe UI Emoji',
'Segoe UI Symbol',
],
},
fontSize: {
'4xs': '0.6rem',
'3xs': '0.675rem',
'2xs': '0.75rem',
xs: '0.8rem',
sm: '0.9rem',
base: '1rem',
lg: '1.12rem',
xl: '1.25rem',
'2xl': '1.5rem',
'3xl': '2rem',
'4xl': '2.5rem',
'5xl': '3rem',
editor: 'var(--editor-font-size)',
shrink: '0.8em',
},
boxShadow: {
DEFAULT: '0 1px 3px 0 var(--shadow)',
lg: '0 10px 15px -3px var(--shadow)',
},
colors: {
transparent: 'transparent',
placeholder: 'var(--textSubtlest)',
shadow: 'var(--shadow)',
backdrop: 'var(--backdrop)',
selection: 'var(--selection)',
// New theme values
surface: 'var(--surface)',
'surface-highlight': 'var(--surfaceHighlight)',
'surface-active': 'var(--surfaceActive)',
text: 'var(--text)',
'text-subtle': 'var(--textSubtle)',
'text-subtlest': 'var(--textSubtlest)',
border: 'var(--border)',
'border-subtle': 'var(--borderSubtle)',
'border-focus': 'var(--borderFocus)',
primary: 'var(--primary)',
danger: 'var(--danger)',
secondary: 'var(--secondary)',
success: 'var(--success)',
info: 'var(--info)',
notice: 'var(--notice)',
warning: 'var(--warning)',
},
},
plugins: [
require('@tailwindcss/container-queries'),
plugin(function ({ addVariant }) {
addVariant('hocus', ['&:hover', '&:focus-visible', '&.focus:focus']);
addVariant('focus-visible-or-class', ['&:focus-visible', '&.focus:focus']);
}),
"./*.{html,ts,tsx}",
"./commands/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}",
"./hooks/**/*.{ts,tsx}",
"./init/**/*.{ts,tsx}",
"./lib/**/*.{ts,tsx}",
"./routes/**/*.{ts,tsx}",
"../../packages/ui/src/**/*.{ts,tsx}",
],
};

View File

@@ -1,12 +1,15 @@
import { listen } from '@tauri-apps/api/event';
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
import { setWindowTheme } from '@yaakapp-internal/mac-window';
import type { ModelPayload } from '@yaakapp-internal/models';
import { getSettings } from './lib/settings';
import type { Appearance } from './lib/theme/appearance';
import { getCSSAppearance, subscribeToPreferredAppearance } from './lib/theme/appearance';
import { getResolvedTheme } from './lib/theme/themes';
import { addThemeStylesToDocument, setThemeOnDocument } from './lib/theme/window';
import { listen } from "@tauri-apps/api/event";
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
import { setWindowTheme } from "@yaakapp-internal/mac-window";
import type { ModelPayload } from "@yaakapp-internal/models";
import { getSettings } from "./lib/settings";
import type { Appearance } from "./lib/theme/appearance";
import {
getCSSAppearance,
subscribeToPreferredAppearance,
} from "./lib/theme/appearance";
import { getResolvedTheme } from "./lib/theme/themes";
import { applyThemeToDocument } from "@yaakapp-internal/theme";
// NOTE: CSS appearance isn't as accurate as getting it async from the window (next step), but we want
// a good appearance guess so we're not waiting too long
@@ -22,15 +25,15 @@ configureTheme().then(
// need to show it here, after configuring the theme for the first time.
await getCurrentWebviewWindow().show();
},
(err) => console.log('Failed to configure theme', err),
(err) => console.log("Failed to configure theme", err),
);
// Listen for settings changes, the re-compute theme
listen<ModelPayload>('model_write', async (event) => {
if (event.payload.change.type !== 'upsert') return;
listen<ModelPayload>("model_write", async (event) => {
if (event.payload.change.type !== "upsert") return;
const model = event.payload.model.model;
if (model !== 'settings' && model !== 'plugin') return;
if (model !== "settings" && model !== "plugin") return;
await configureTheme();
}).catch(console.error);
@@ -42,8 +45,7 @@ async function configureTheme() {
settings.themeLight,
settings.themeDark,
);
addThemeStylesToDocument(theme.active);
setThemeOnDocument(theme.active);
applyThemeToDocument(theme.active);
if (theme.active.base.surface != null) {
setWindowTheme(theme.active.base.surface);
}

View File

@@ -15,9 +15,16 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@yaakapp-internal/theme": ["../../packages/theme/src/index.ts"],
"@yaakapp-internal/theme/*": ["../../packages/theme/src/*"],
"@yaakapp-internal/ui": ["../../packages/ui/src/index.ts"],
"@yaakapp-internal/ui/*": ["../../packages/ui/src/*"],
},
},
"include": ["."],
"exclude": ["vite.config.ts"],
"references": [{ "path": "./tsconfig.node.json" }]
"references": [{ "path": "./tsconfig.node.json" }],
}

View File

@@ -1,12 +1,27 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Yaak Proxy</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/main.tsx"></script>
</body>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Yaak Proxy</title>
<style>
html,
body {
background-color: white;
}
@media (prefers-color-scheme: dark) {
html,
body {
background-color: #1b1a29;
}
}
</style>
</head>
<body class="text-base">
<div id="root"></div>
<script type="module" src="/theme.ts"></script>
<script type="module" src="/main.tsx"></script>
</body>
</html>

View File

@@ -1,95 +1,92 @@
:root {
color: #f4efe7;
background:
radial-gradient(circle at top, rgba(217, 119, 6, 0.35), transparent 45%),
linear-gradient(180deg, #18212b 0%, #0f141a 100%);
font-family: "IBM Plex Sans", "Segoe UI", sans-serif;
}
@tailwind base;
@tailwind components;
@tailwind utilities;
* {
box-sizing: border-box;
}
@layer base {
html,
body,
#root {
@apply w-full h-full overflow-hidden text-text bg-surface;
}
html,
body,
#root {
margin: 0;
min-height: 100%;
}
:root {
--font-family-interface: "";
--font-family-editor: "";
}
body {
min-height: 100vh;
}
:root {
font-variant-ligatures: none;
}
.app-shell {
min-height: 100vh;
display: grid;
place-items: center;
padding: 24px;
}
html[data-platform="linux"] {
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
.hero-card {
width: min(680px, 100%);
padding: 40px;
border: 1px solid rgba(244, 239, 231, 0.12);
border-radius: 28px;
background: rgba(9, 12, 16, 0.7);
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.35);
backdrop-filter: blur(18px);
}
::selection {
@apply bg-selection;
}
.eyebrow {
margin: 0 0 12px;
font-size: 12px;
letter-spacing: 0.24em;
text-transform: uppercase;
color: #f6ad55;
}
:not(a),
:not(input):not(textarea),
:not(input):not(textarea)::after,
:not(input):not(textarea)::before {
@apply select-none cursor-default;
}
h1 {
margin: 0;
font-size: clamp(44px, 8vw, 84px);
line-height: 0.95;
}
input,
textarea {
&::placeholder {
@apply text-placeholder;
}
}
.lede {
margin: 20px 0 0;
max-width: 48ch;
font-size: 18px;
line-height: 1.6;
color: rgba(244, 239, 231, 0.78);
}
a,
a[href] * {
@apply cursor-pointer !important;
}
.controls {
margin-top: 28px;
display: flex;
gap: 12px;
}
table th {
@apply text-left;
}
.btn {
border: 0;
border-radius: 10px;
padding: 10px 14px;
background: #f6ad55;
color: #111;
font-weight: 700;
cursor: pointer;
}
:not(iframe) {
&::-webkit-scrollbar,
&::-webkit-scrollbar-corner {
@apply w-[8px] h-[8px] bg-transparent;
}
.btn.ghost {
background: rgba(255, 255, 255, 0.12);
color: #f4efe7;
}
&::-webkit-scrollbar-track {
@apply bg-transparent;
}
.btn:disabled {
opacity: 0.6;
cursor: default;
}
&::-webkit-scrollbar-thumb {
@apply bg-text-subtlest rounded-[4px] opacity-20;
}
.status {
margin-top: 14px;
display: flex;
gap: 16px;
color: rgba(244, 239, 231, 0.88);
font-size: 14px;
&::-webkit-scrollbar-thumb:hover {
@apply opacity-40 !important;
}
}
.hide-scrollbars {
&::-webkit-scrollbar-corner,
&::-webkit-scrollbar {
@apply hidden !important;
}
}
.rtl {
direction: rtl;
}
:root {
color-scheme: light dark;
--transition-duration: 100ms ease-in-out;
--color-white: 255 100% 100%;
--color-black: 255 0% 0%;
}
}

View File

@@ -1,4 +1,5 @@
import "./main.css";
import { Button } from "@yaakapp-internal/ui";
import { invoke } from "@tauri-apps/api/core";
import { StrictMode } from "react";
import { useState } from "react";
@@ -45,25 +46,38 @@ function App() {
}
return (
<main className="app-shell">
<section className="hero-card">
<p className="eyebrow">Monorepo Smoke Test</p>
<h1>Yaak Proxy</h1>
<p className="lede">
This is a minimal proxy app stub running on the new `apps/yaak-proxy`
and `crates-tauri/yaak-app-proxy` structure.
</p>
<div className="controls">
<button className="btn" disabled={busy} onClick={startProxy}>
Start Proxy
</button>
<button className="btn ghost" disabled={busy} onClick={stopProxy}>
Stop Proxy
</button>
</div>
<div className="status">
<span>Status: {status}</span>
{port != null ? <span>Port: {port}</span> : null}
<main className="h-full w-full overflow-auto p-6">
<section className="flex items-start">
<div className="flex w-full max-w-xl flex-col gap-4">
<div>
<h1 className="text-2xl font-semibold text-text">Yaak Proxy</h1>
<p className="mt-2 text-sm text-text-subtle">Status: {status}</p>
<p className="mt-1 text-sm text-text-subtle">
Port: {port ?? "Not running"}
</p>
</div>
<div className="flex flex-wrap gap-3">
<Button
disabled={busy}
onClick={startProxy}
size="sm"
tone="primary"
>
Start Proxy
</Button>
<Button
disabled={busy}
onClick={stopProxy}
size="sm"
variant="border"
>
Stop Proxy
</Button>
<Button size="sm" type="button">
Shared Button
</Button>
</div>
</div>
</section>
</main>

View File

@@ -9,6 +9,8 @@
"lint": "tsc --noEmit"
},
"dependencies": {
"@yaakapp-internal/theme": "^1.0.0",
"@yaakapp-internal/ui": "^1.0.0",
"@tauri-apps/api": "^2.9.1",
"react": "^19.1.0",
"react-dom": "^19.1.0"

View File

@@ -0,0 +1,7 @@
module.exports = {
plugins: [
require("@tailwindcss/nesting")(require("postcss-nesting")),
require("tailwindcss"),
require("autoprefixer"),
],
};

View File

@@ -0,0 +1,7 @@
const sharedConfig = require("@yaakapp-internal/tailwind-config");
/** @type {import('tailwindcss').Config} */
module.exports = {
...sharedConfig,
content: ["./*.{html,ts,tsx}", "../../packages/ui/src/**/*.{ts,tsx}"],
};

9
apps/yaak-proxy/theme.ts Normal file
View File

@@ -0,0 +1,9 @@
import {
applyThemeToDocument,
defaultDarkTheme,
platformFromUserAgent,
setPlatformOnDocument,
} from "@yaakapp-internal/theme";
setPlatformOnDocument(platformFromUserAgent(navigator.userAgent));
applyThemeToDocument(defaultDarkTheme);

View File

@@ -14,9 +14,16 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@yaakapp-internal/theme": ["../../packages/theme/src/index.ts"],
"@yaakapp-internal/theme/*": ["../../packages/theme/src/*"],
"@yaakapp-internal/ui": ["../../packages/ui/src/index.ts"],
"@yaakapp-internal/ui/*": ["../../packages/ui/src/*"],
},
},
"include": ["."],
"exclude": ["vite.config.ts"],
"references": [{ "path": "./tsconfig.node.json" }]
"references": [{ "path": "./tsconfig.node.json" }],
}

105
package-lock.json generated
View File

@@ -8,6 +8,9 @@
"name": "yaak-monorepo",
"version": "0.0.0",
"workspaces": [
"packages/ui",
"packages/theme",
"packages/tailwind-config",
"packages/common-lib",
"packages/plugin-runtime",
"packages/plugin-runtime-types",
@@ -167,6 +170,8 @@
"@types/uuid": "^10.0.0",
"@types/whatwg-mimetype": "^3.0.2",
"@vitejs/plugin-react": "^4.6.0",
"@yaakapp-internal/theme": "^1.0.0",
"@yaakapp-internal/ui": "^1.0.0",
"autoprefixer": "^10.4.21",
"decompress": "^4.2.1",
"internal-ip": "^8.0.0",
@@ -220,6 +225,8 @@
"version": "1.0.0",
"dependencies": {
"@tauri-apps/api": "^2.9.1",
"@yaakapp-internal/theme": "^1.0.0",
"@yaakapp-internal/ui": "^1.0.0",
"react": "^19.1.0",
"react-dom": "^19.1.0"
},
@@ -305,7 +312,6 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
@@ -1666,7 +1672,6 @@
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
@@ -1688,7 +1693,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
@@ -1704,7 +1708,6 @@
"version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@@ -3255,7 +3258,6 @@
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@tailwindcss/container-queries/-/container-queries-0.1.1.tgz",
"integrity": "sha512-p18dswChx6WnTSaJCSGx6lTmrGzNNvm2FtXmiO6AuA1V4U5REyoqwmT6kgAsIMdjo07QdAfYXHJ4hnMtfHzWgA==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"tailwindcss": ">=3.2.0"
@@ -4435,6 +4437,10 @@
"resolved": "crates/yaak-sync",
"link": true
},
"node_modules/@yaakapp-internal/tailwind-config": {
"resolved": "packages/tailwind-config",
"link": true
},
"node_modules/@yaakapp-internal/tauri-client": {
"resolved": "crates-tauri/yaak-app-client",
"link": true
@@ -4447,6 +4453,14 @@
"resolved": "crates/yaak-templates",
"link": true
},
"node_modules/@yaakapp-internal/theme": {
"resolved": "packages/theme",
"link": true
},
"node_modules/@yaakapp-internal/ui": {
"resolved": "packages/ui",
"link": true
},
"node_modules/@yaakapp-internal/ws": {
"resolved": "crates/yaak-ws",
"link": true
@@ -4755,14 +4769,12 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
"dev": true,
"license": "MIT"
},
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"license": "ISC",
"dependencies": {
"normalize-path": "^3.0.0",
@@ -4776,7 +4788,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
@@ -4789,7 +4800,6 @@
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
"dev": true,
"license": "MIT"
},
"node_modules/argparse": {
@@ -5010,7 +5020,6 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -5274,7 +5283,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
@@ -5406,7 +5414,6 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true,
"license": "MIT",
"dependencies": {
"anymatch": "~3.1.2",
@@ -6087,7 +6094,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true,
"license": "MIT",
"bin": {
"cssesc": "bin/cssesc"
@@ -6496,7 +6502,6 @@
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/diff": {
@@ -6541,7 +6546,6 @@
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true,
"license": "MIT"
},
"node_modules/dot-case": {
@@ -6908,7 +6912,7 @@
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
"integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
"dev": true,
"devOptional": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
@@ -7297,7 +7301,6 @@
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
@@ -7575,7 +7578,6 @@
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@@ -7752,7 +7754,7 @@
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz",
"integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==",
"dev": true,
"devOptional": true,
"license": "MIT",
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
@@ -8597,7 +8599,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"license": "MIT",
"dependencies": {
"binary-extensions": "^2.0.0"
@@ -8638,7 +8639,6 @@
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
"dev": true,
"license": "MIT",
"dependencies": {
"hasown": "^2.0.2"
@@ -9073,7 +9073,6 @@
"version": "1.21.7",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"dev": true,
"license": "MIT",
"bin": {
"jiti": "bin/jiti.js"
@@ -9392,7 +9391,6 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14"
@@ -10797,7 +10795,6 @@
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"any-promise": "^1.0.0",
@@ -10829,7 +10826,6 @@
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"dev": true,
"funding": [
{
"type": "github",
@@ -10987,7 +10983,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -11943,7 +11938,6 @@
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true,
"license": "MIT"
},
"node_modules/path-scurry": {
@@ -12042,7 +12036,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@@ -12101,7 +12094,6 @@
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
"integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
@@ -12129,7 +12121,6 @@
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"dev": true,
"funding": [
{
"type": "opencollective",
@@ -12158,7 +12149,6 @@
"version": "15.1.0",
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
"dev": true,
"license": "MIT",
"dependencies": {
"postcss-value-parser": "^4.0.0",
@@ -12176,7 +12166,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
"integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
"dev": true,
"funding": [
{
"type": "opencollective",
@@ -12202,7 +12191,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
"integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
"dev": true,
"funding": [
{
"type": "opencollective",
@@ -12353,7 +12341,6 @@
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
"dev": true,
"license": "MIT",
"dependencies": {
"cssesc": "^3.0.0",
@@ -12367,7 +12354,6 @@
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true,
"license": "MIT"
},
"node_modules/postman-collection": {
@@ -12747,7 +12733,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
"dev": true,
"license": "MIT",
"dependencies": {
"pify": "^2.3.0"
@@ -12757,7 +12742,6 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -12812,7 +12796,6 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"license": "MIT",
"dependencies": {
"picomatch": "^2.2.1"
@@ -12825,7 +12808,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
@@ -13060,7 +13042,6 @@
"version": "1.22.11",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
"integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-core-module": "^2.16.1",
@@ -13090,7 +13071,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"dev": true,
"devOptional": true,
"license": "MIT",
"funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
@@ -13830,7 +13811,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
@@ -14296,7 +14276,6 @@
"version": "3.35.1",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
"integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.2",
@@ -14319,7 +14298,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
@@ -14360,7 +14338,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -14563,7 +14540,6 @@
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
"integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
@@ -14601,7 +14577,6 @@
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.3"
@@ -14614,7 +14589,6 @@
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
"dev": true,
"funding": [
{
"type": "opencollective",
@@ -14659,7 +14633,6 @@
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
"dev": true,
"license": "MIT",
"dependencies": {
"any-promise": "^1.0.0"
@@ -14669,7 +14642,6 @@
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
"dev": true,
"license": "MIT",
"dependencies": {
"thenify": ">= 3.1.0 < 4"
@@ -14740,7 +14712,6 @@
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
@@ -14871,7 +14842,6 @@
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/tslib": {
@@ -14884,7 +14854,7 @@
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
"dev": true,
"devOptional": true,
"license": "MIT",
"dependencies": {
"esbuild": "~0.27.0",
@@ -15280,7 +15250,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true,
"license": "MIT"
},
"node_modules/uuid": {
@@ -16165,6 +16134,34 @@
"undici-types": "~7.16.0"
}
},
"packages/tailwind-config": {
"name": "@yaakapp-internal/tailwind-config",
"version": "1.0.0",
"dependencies": {
"@tailwindcss/container-queries": "^0.1.1",
"tailwindcss": "^3.4.17"
}
},
"packages/theme": {
"version": "1.0.0",
"dependencies": {
"@tauri-apps/api": "^2.9.1",
"@yaakapp-internal/plugins": "^1.0.0",
"parse-color": "^1.0.0"
}
},
"packages/ui": {
"name": "@yaakapp-internal/ui",
"version": "1.0.0",
"dependencies": {
"react": "^19.1.0",
"react-dom": "^19.1.0"
},
"peerDependencies": {
"react": "^19.1.0",
"react-dom": "^19.1.0"
}
},
"plugins-external/faker": {
"name": "@yaak/faker",
"version": "1.1.1",

View File

@@ -7,6 +7,9 @@
"url": "git+https://github.com/mountain-loop/yaak.git"
},
"workspaces": [
"packages/ui",
"packages/theme",
"packages/tailwind-config",
"packages/common-lib",
"packages/plugin-runtime",
"packages/plugin-runtime-types",

View File

@@ -0,0 +1,136 @@
const plugin = require("tailwindcss/plugin");
const sizes = {
"2xs": "1.4rem",
xs: "1.8rem",
sm: "2.0rem",
md: "2.3rem",
lg: "2.6rem",
};
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class", '[data-resolved-appearance="dark"]'],
theme: {
extend: {
keyframes: {
blinkRing: {
"0%, 49%": { "--tw-ring-color": "var(--primary)" },
"50%, 99%": { "--tw-ring-color": "transparent" },
"100%": { "--tw-ring-color": "var(--primary)" },
},
},
animation: {
blinkRing: "blinkRing 150ms step-start 400ms infinite",
},
opacity: {
disabled: "0.3",
},
fontSize: {
xs: "0.8rem",
},
height: sizes,
width: sizes,
minHeight: sizes,
minWidth: sizes,
lineHeight: {
// HACK: Minus 2 to account for borders inside inputs
xs: "calc(1.75rem - 2px)",
sm: "calc(2.0rem - 2px)",
md: "calc(2.5rem - 2px)",
},
transitionProperty: {
grid: "grid",
},
},
fontFamily: {
mono: [
"var(--font-family-editor)",
"JetBrains Mono",
"ui-monospace",
"SFMono-Regular",
"Menlo",
"Monaco",
"Fira Code",
"Ubuntu Mono",
"Consolas",
"Liberation Mono",
"Courier New",
"DejaVu Sans Mono",
"Hack",
"monospace",
],
sans: [
"var(--font-family-interface)",
"Inter UI",
"-apple-system",
"BlinkMacSystemFont",
"Segoe UI",
"Roboto",
"Oxygen-Sans",
"Ubuntu",
"Cantarell",
"Helvetica Neue",
"sans-serif",
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol",
],
},
fontSize: {
"4xs": "0.6rem",
"3xs": "0.675rem",
"2xs": "0.75rem",
xs: "0.8rem",
sm: "0.9rem",
base: "1rem",
lg: "1.12rem",
xl: "1.25rem",
"2xl": "1.5rem",
"3xl": "2rem",
"4xl": "2.5rem",
"5xl": "3rem",
editor: "var(--editor-font-size)",
shrink: "0.8em",
},
boxShadow: {
DEFAULT: "0 1px 3px 0 var(--shadow)",
lg: "0 10px 15px -3px var(--shadow)",
},
colors: {
transparent: "transparent",
placeholder: "var(--textSubtlest)",
shadow: "var(--shadow)",
backdrop: "var(--backdrop)",
selection: "var(--selection)",
// New theme values
surface: "var(--surface)",
"surface-highlight": "var(--surfaceHighlight)",
"surface-active": "var(--surfaceActive)",
text: "var(--text)",
"text-subtle": "var(--textSubtle)",
"text-subtlest": "var(--textSubtlest)",
border: "var(--border)",
"border-subtle": "var(--borderSubtle)",
"border-focus": "var(--borderFocus)",
primary: "var(--primary)",
danger: "var(--danger)",
secondary: "var(--secondary)",
success: "var(--success)",
info: "var(--info)",
notice: "var(--notice)",
warning: "var(--warning)",
},
},
plugins: [
require("@tailwindcss/container-queries"),
plugin(function ({ addVariant }) {
addVariant("hocus", ["&:hover", "&:focus-visible", "&.focus:focus"]);
addVariant("focus-visible-or-class", [
"&:focus-visible",
"&.focus:focus",
]);
}),
],
};

5
packages/tailwind-config/index.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
import type { Config } from "tailwindcss";
declare const config: Config;
export = config;

View File

@@ -0,0 +1,11 @@
{
"name": "@yaakapp-internal/tailwind-config",
"private": true,
"version": "1.0.0",
"main": "index.cjs",
"types": "index.d.ts",
"dependencies": {
"@tailwindcss/container-queries": "^0.1.1",
"tailwindcss": "^3.4.17"
}
}

View File

@@ -0,0 +1,13 @@
{
"name": "@yaakapp-internal/theme",
"private": true,
"version": "1.0.0",
"type": "module",
"main": "src/index.ts",
"types": "src/index.ts",
"dependencies": {
"@tauri-apps/api": "^2.9.1",
"@yaakapp-internal/plugins": "^1.0.0",
"parse-color": "^1.0.0"
}
}

View File

@@ -0,0 +1,44 @@
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
export type Appearance = "light" | "dark";
export function getCSSAppearance(): Appearance {
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}
export async function getWindowAppearance(): Promise<Appearance> {
const appearance = await getCurrentWebviewWindow().theme();
return appearance ?? getCSSAppearance();
}
export function subscribeToWindowAppearanceChange(
cb: (appearance: Appearance) => void,
): () => void {
const container = {
unsubscribe: () => {},
};
getCurrentWebviewWindow()
.onThemeChanged((theme) => {
cb(theme.payload);
})
.then((listener) => {
container.unsubscribe = listener;
});
return () => container.unsubscribe();
}
export function resolveAppearance(
preferredAppearance: Appearance,
appearanceSetting: string,
): Appearance {
const appearance = appearanceSetting === "system" ? preferredAppearance : appearanceSetting;
return appearance === "dark" ? "dark" : "light";
}
export function subscribeToPreferredAppearance(cb: (appearance: Appearance) => void) {
cb(getCSSAppearance());
getWindowAppearance().then(cb);
subscribeToWindowAppearanceChange(cb);
}

View File

@@ -0,0 +1,76 @@
import type { Theme } from "@yaakapp-internal/plugins";
export const defaultDarkTheme: Theme = {
id: "yaak-dark",
label: "Yaak",
dark: true,
base: {
surface: "hsl(244,23%,14%)",
surfaceHighlight: "hsl(244,23%,20%)",
text: "hsl(245,23%,85%)",
textSubtle: "hsl(245,18%,58%)",
textSubtlest: "hsl(245,18%,45%)",
border: "hsl(244,23%,25%)",
primary: "hsl(266,100%,79%)",
secondary: "hsl(245,23%,60%)",
info: "hsl(206,100%,63%)",
success: "hsl(150,99%,44%)",
notice: "hsl(48,80%,63%)",
warning: "hsl(28,100%,61%)",
danger: "hsl(342,90%,68%)",
},
components: {
button: {
primary: "hsl(266,100%,71.1%)",
secondary: "hsl(244,23%,54%)",
info: "hsl(206,100%,56.7%)",
success: "hsl(150,99%,37.4%)",
notice: "hsl(48,80%,50.4%)",
warning: "hsl(28,100%,54.9%)",
danger: "hsl(342,90%,61.2%)",
},
dialog: {
border: "hsl(244,23%,24%)",
},
sidebar: {
surface: "hsl(243,23%,16%)",
border: "hsl(244,23%,22%)",
},
responsePane: {
surface: "hsl(243,23%,16%)",
border: "hsl(246,23%,23%)",
},
appHeader: {
surface: "hsl(244,23%,12%)",
border: "hsl(244,23%,21%)",
},
},
};
export const defaultLightTheme: Theme = {
id: "yaak-light",
label: "Yaak",
dark: false,
base: {
surface: "hsl(0,0%,100%)",
surfaceHighlight: "hsl(218,24%,87%)",
text: "hsl(217,24%,10%)",
textSubtle: "hsl(217,24%,40%)",
textSubtlest: "hsl(217,24%,58%)",
border: "hsl(217,22%,90%)",
primary: "hsl(266,100%,60%)",
secondary: "hsl(220,24%,50%)",
info: "hsl(206,100%,40%)",
success: "hsl(139,66%,34%)",
notice: "hsl(45,100%,34%)",
warning: "hsl(30,100%,36%)",
danger: "hsl(335,75%,48%)",
},
components: {
sidebar: {
surface: "hsl(220,20%,98%)",
border: "hsl(217,22%,88%)",
surfaceHighlight: "hsl(217,25%,90%)",
},
},
};

View File

@@ -0,0 +1,26 @@
export type { Appearance } from "./appearance";
export {
getCSSAppearance,
getWindowAppearance,
resolveAppearance,
subscribeToPreferredAppearance,
subscribeToWindowAppearanceChange,
} from "./appearance";
export { defaultDarkTheme, defaultLightTheme } from "./defaultThemes";
export { YaakColor } from "./yaakColor";
export type {
DocumentPlatform,
YaakColorKey,
YaakColors,
YaakTheme,
} from "./window";
export {
addThemeStylesToDocument,
applyThemeToDocument,
completeTheme,
getThemeCSS,
indent,
platformFromUserAgent,
setThemeOnDocument,
setPlatformOnDocument,
} from "./window";

View File

@@ -0,0 +1,430 @@
import type { Theme, ThemeComponentColors } from "@yaakapp-internal/plugins";
import { defaultDarkTheme, defaultLightTheme } from "./defaultThemes";
import { YaakColor } from "./yaakColor";
export type YaakColors = {
surface: YaakColor;
surfaceHighlight?: YaakColor;
surfaceActive?: YaakColor;
text: YaakColor;
textSubtle?: YaakColor;
textSubtlest?: YaakColor;
border?: YaakColor;
borderSubtle?: YaakColor;
borderFocus?: YaakColor;
shadow?: YaakColor;
backdrop?: YaakColor;
selection?: YaakColor;
primary?: YaakColor;
secondary?: YaakColor;
info?: YaakColor;
success?: YaakColor;
notice?: YaakColor;
warning?: YaakColor;
danger?: YaakColor;
};
export type YaakTheme = {
id: string;
name: string;
base: YaakColors;
components?: Partial<{
dialog: Partial<YaakColors>;
menu: Partial<YaakColors>;
toast: Partial<YaakColors>;
sidebar: Partial<YaakColors>;
responsePane: Partial<YaakColors>;
appHeader: Partial<YaakColors>;
button: Partial<YaakColors>;
banner: Partial<YaakColors>;
templateTag: Partial<YaakColors>;
urlBar: Partial<YaakColors>;
editor: Partial<YaakColors>;
input: Partial<YaakColors>;
}>;
};
export type YaakColorKey = keyof ThemeComponentColors;
export type DocumentPlatform = "linux" | "macos" | "windows" | "unknown";
type ComponentName = keyof NonNullable<YaakTheme["components"]>;
type CSSVariables = Record<YaakColorKey, string | undefined>;
function themeVariables(
theme: Theme,
component?: ComponentName,
base?: CSSVariables,
): CSSVariables | null {
const cmp =
component == null
? theme.base
: (theme.components?.[component] ?? ({} as ThemeComponentColors));
const color = (value: string | undefined) => yc(theme, value);
const vars: CSSVariables = {
surface: cmp.surface,
surfaceHighlight:
cmp.surfaceHighlight ?? color(cmp.surface)?.lift(0.06).css(),
surfaceActive:
cmp.surfaceActive ??
color(cmp.primary)?.lower(0.2).translucify(0.8).css(),
backdrop:
cmp.backdrop ?? color(cmp.surface)?.lower(0.2).translucify(0.2).css(),
selection:
cmp.selection ?? color(cmp.primary)?.lower(0.1).translucify(0.7).css(),
border: cmp.border ?? color(cmp.surface)?.lift(0.11)?.css(),
borderSubtle: cmp.borderSubtle ?? color(cmp.border)?.lower(0.06)?.css(),
borderFocus: color(cmp.info)?.translucify(0.5)?.css(),
text: cmp.text,
textSubtle: cmp.textSubtle ?? color(cmp.text)?.lower(0.2)?.css(),
textSubtlest: cmp.textSubtlest ?? color(cmp.text)?.lower(0.3)?.css(),
shadow:
cmp.shadow ??
YaakColor.black()
.translucify(theme.dark ? 0.7 : 0.93)
.css(),
primary: cmp.primary,
secondary: cmp.secondary,
info: cmp.info,
success: cmp.success,
notice: cmp.notice,
warning: cmp.warning,
danger: cmp.danger,
};
for (const [key, value] of Object.entries(vars)) {
if (!value && base?.[key as YaakColorKey]) {
vars[key as YaakColorKey] = base[key as YaakColorKey];
}
}
return vars;
}
function templateTagColorVariables(
color: YaakColor | null,
): Partial<CSSVariables> {
if (color == null) return {};
return {
text: color.lift(0.7).css(),
textSubtle: color.lift(0.4).css(),
textSubtlest: color.css(),
surface: color.lower(0.2).translucify(0.8).css(),
border: color.translucify(0.6).css(),
borderSubtle: color.translucify(0.8).css(),
surfaceHighlight: color.lower(0.1).translucify(0.7).css(),
};
}
function toastColorVariables(color: YaakColor | null): Partial<CSSVariables> {
if (color == null) return {};
return {
text: color.lift(0.8).css(),
textSubtle: color.lift(0.8).translucify(0.3).css(),
surface: color.translucify(0.9).css(),
surfaceHighlight: color.translucify(0.8).css(),
border: color.lift(0.3).translucify(0.6).css(),
};
}
function bannerColorVariables(color: YaakColor | null): Partial<CSSVariables> {
if (color == null) return {};
return {
text: color.lift(0.8).css(),
textSubtle: color.translucify(0.3).css(),
textSubtlest: color.translucify(0.6).css(),
surface: color.translucify(0.95).css(),
border: color.lift(0.3).translucify(0.8).css(),
};
}
function buttonSolidColorVariables(
color: YaakColor | null,
isDefault = false,
): Partial<CSSVariables> {
if (color == null) return {};
const theme: Partial<ThemeComponentColors> = {
text: "white",
surface: color.lower(0.3).css(),
surfaceHighlight: color.lower(0.1).css(),
border: color.css(),
};
if (isDefault) {
theme.text = undefined;
theme.surface = undefined;
theme.surfaceHighlight = color.lift(0.08).css();
}
return theme;
}
function buttonBorderColorVariables(
color: YaakColor | null,
isDefault = false,
): Partial<CSSVariables> {
if (color == null) return {};
const vars: Partial<CSSVariables> = {
text: color.lift(0.8).css(),
textSubtle: color.lift(0.55).css(),
textSubtlest: color.lift(0.4).translucify(0.6).css(),
surfaceHighlight: color.translucify(0.8).css(),
borderSubtle: color.translucify(0.5).css(),
border: color.translucify(0.3).css(),
};
if (isDefault) {
vars.borderSubtle = color.lift(0.28).css();
vars.border = color.lift(0.5).css();
}
return vars;
}
function variablesToCSS(
selector: string | null,
vars: Partial<CSSVariables> | null,
): string | null {
if (vars == null) return null;
const css = Object.entries(vars)
.filter(([, value]) => value)
.map(([name, value]) => `--${name}: ${value};`)
.join("\n");
return selector == null ? css : `${selector} {\n${indent(css)}\n}`;
}
function componentCSS(theme: Theme, component: ComponentName): string | null {
if (theme.components == null) return null;
return variablesToCSS(
`.x-theme-${component}`,
themeVariables(theme, component),
);
}
function buttonCSS(
theme: Theme,
colorKey: YaakColorKey,
colors?: ThemeComponentColors,
): string | null {
const color = yc(theme, colors?.[colorKey]);
if (color == null) return null;
return [
variablesToCSS(
`.x-theme-button--solid--${colorKey}`,
buttonSolidColorVariables(color),
),
variablesToCSS(
`.x-theme-button--border--${colorKey}`,
buttonBorderColorVariables(color),
),
].join("\n\n");
}
function bannerCSS(
theme: Theme,
colorKey: YaakColorKey,
colors?: ThemeComponentColors,
): string | null {
const color = yc(theme, colors?.[colorKey]);
if (color == null) return null;
return variablesToCSS(
`.x-theme-banner--${colorKey}`,
bannerColorVariables(color),
);
}
function toastCSS(
theme: Theme,
colorKey: YaakColorKey,
colors?: ThemeComponentColors,
): string | null {
const color = yc(theme, colors?.[colorKey]);
if (color == null) return null;
return variablesToCSS(
`.x-theme-toast--${colorKey}`,
toastColorVariables(color),
);
}
function templateTagCSS(
theme: Theme,
colorKey: YaakColorKey,
colors?: ThemeComponentColors,
): string | null {
const color = yc(theme, colors?.[colorKey]);
if (color == null) return null;
return variablesToCSS(
`.x-theme-templateTag--${colorKey}`,
templateTagColorVariables(color),
);
}
export function getThemeCSS(theme: Theme): string {
theme.components = theme.components ?? {};
theme.components.toast =
theme.components.toast ?? theme.components.menu ?? {};
const { components, id, label } = theme;
const colors = Object.keys(theme.base).reduce((prev, key) => {
return { ...prev, [key]: theme.base[key as YaakColorKey] };
}, {} as ThemeComponentColors);
let themeCSS = "";
try {
const baseCss = variablesToCSS(null, themeVariables(theme));
themeCSS = [
baseCss,
...Object.keys(components).map((key) =>
componentCSS(theme, key as ComponentName),
),
variablesToCSS(
".x-theme-button--solid--default",
buttonSolidColorVariables(yc(theme, theme.base.surface), true),
),
variablesToCSS(
".x-theme-button--border--default",
buttonBorderColorVariables(yc(theme, theme.base.surface), true),
),
...Object.keys(colors).map((key) =>
buttonCSS(
theme,
key as YaakColorKey,
theme.components?.button ?? colors,
),
),
...Object.keys(colors).map((key) =>
bannerCSS(
theme,
key as YaakColorKey,
theme.components?.banner ?? colors,
),
),
...Object.keys(colors).map((key) =>
toastCSS(
theme,
key as YaakColorKey,
theme.components?.banner ?? colors,
),
),
...Object.keys(colors).map((key) =>
templateTagCSS(
theme,
key as YaakColorKey,
theme.components?.templateTag ?? colors,
),
),
].join("\n\n");
} catch (err) {
console.error("Failed to generate CSS", err);
}
return [
`/* ${label} */`,
`[data-theme="${id}"] {`,
indent(themeCSS),
"}",
].join("\n");
}
export function addThemeStylesToDocument(rawTheme: Theme | null) {
if (rawTheme == null) {
console.error("Failed to add theme styles: theme is null");
return;
}
const theme = completeTheme(rawTheme);
let styleEl = document.head.querySelector("style[data-theme]");
if (!styleEl) {
styleEl = document.createElement("style");
document.head.appendChild(styleEl);
}
styleEl.setAttribute("data-theme", theme.id);
styleEl.setAttribute("data-updated-at", new Date().toISOString());
styleEl.textContent = getThemeCSS(theme);
}
export function setThemeOnDocument(theme: Theme | null) {
if (theme == null) {
console.error("Failed to set theme: theme is null");
return;
}
document.documentElement.setAttribute("data-theme", theme.id);
}
export function applyThemeToDocument(theme: Theme | null) {
addThemeStylesToDocument(theme);
setThemeOnDocument(theme);
}
export function platformFromUserAgent(userAgent: string): DocumentPlatform {
const normalized = userAgent.toLowerCase();
if (normalized.includes("linux")) return "linux";
if (normalized.includes("mac os") || normalized.includes("macintosh"))
return "macos";
if (normalized.includes("win")) return "windows";
return "unknown";
}
export function setPlatformOnDocument(platform: string | null | undefined) {
const normalized =
platform === "linux" || platform === "macos" || platform === "windows"
? platform
: "unknown";
document.documentElement.setAttribute("data-platform", normalized);
}
export function indent(text: string, space = " "): string {
return text
.split("\n")
.map((line) => space + line)
.join("\n");
}
function yc<T extends string | null | undefined>(
theme: Theme,
value: T,
): T extends string ? YaakColor : null {
if (value == null) return null as never;
return new YaakColor(value, theme.dark ? "dark" : "light") as never;
}
export function completeTheme(theme: Theme): Theme {
const fallback = theme.dark ? defaultDarkTheme.base : defaultLightTheme.base;
const color = (value: string | null | undefined) => yc(theme, value);
theme.base.primary ??= fallback.primary;
theme.base.secondary ??= fallback.secondary;
theme.base.info ??= fallback.info;
theme.base.success ??= fallback.success;
theme.base.notice ??= fallback.notice;
theme.base.warning ??= fallback.warning;
theme.base.danger ??= fallback.danger;
theme.base.surface ??= fallback.surface;
theme.base.surfaceHighlight ??= color(theme.base.surface)?.lift(0.06)?.css();
theme.base.surfaceActive ??= color(theme.base.primary)
?.lower(0.2)
.translucify(0.8)
.css();
theme.base.border ??= color(theme.base.surface)?.lift(0.12)?.css();
theme.base.borderSubtle ??= color(theme.base.border)?.lower(0.08)?.css();
theme.base.text ??= fallback.text;
theme.base.textSubtle ??= color(theme.base.text)?.lower(0.3)?.css();
theme.base.textSubtlest ??= color(theme.base.text)?.lower(0.5)?.css();
return theme;
}

View File

@@ -0,0 +1,153 @@
import parseColor from "parse-color";
export class YaakColor {
private readonly appearance: "dark" | "light" = "light";
private hue = 0;
private saturation = 0;
private lightness = 0;
private alpha = 1;
constructor(cssColor: string, appearance: "dark" | "light" = "light") {
try {
this.set(cssColor);
this.appearance = appearance;
} catch (err) {
console.log("Failed to parse CSS color", cssColor, err);
}
}
static transparent(): YaakColor {
return new YaakColor("rgb(0,0,0)", "light").translucify(1);
}
static white(): YaakColor {
return new YaakColor("rgb(0,0,0)", "light").lower(1);
}
static black(): YaakColor {
return new YaakColor("rgb(0,0,0)", "light").lift(1);
}
set(cssColor: string): YaakColor {
let fixedCssColor = cssColor;
if (cssColor.startsWith("#") && cssColor.length === 9) {
const [r, g, b, a] = hexToRgba(cssColor);
fixedCssColor = `rgba(${r},${g},${b},${a})`;
}
const { hsla } = parseColor(fixedCssColor);
this.hue = hsla[0];
this.saturation = hsla[1];
this.lightness = hsla[2];
this.alpha = hsla[3] ?? 1;
return this;
}
clone(): YaakColor {
return new YaakColor(this.css(), this.appearance);
}
lower(mod: number): YaakColor {
return this.appearance === "dark" ? this._darken(mod) : this._lighten(mod);
}
lift(mod: number): YaakColor {
return this.appearance === "dark" ? this._lighten(mod) : this._darken(mod);
}
minLightness(n: number): YaakColor {
const color = this.clone();
if (color.lightness < n) {
color.lightness = n;
}
return color;
}
isDark(): boolean {
return this.lightness < 50;
}
translucify(mod: number): YaakColor {
const color = this.clone();
color.alpha = color.alpha - color.alpha * mod;
return color;
}
opacify(mod: number): YaakColor {
const color = this.clone();
color.alpha = this.alpha + (100 - this.alpha) * mod;
return color;
}
desaturate(mod: number): YaakColor {
const color = this.clone();
color.saturation = color.saturation - color.saturation * mod;
return color;
}
saturate(mod: number): YaakColor {
const color = this.clone();
color.saturation = this.saturation + (100 - this.saturation) * mod;
return color;
}
lighterThan(color: YaakColor): boolean {
return this.lightness > color.lightness;
}
css(): string {
const [r, g, b] = parseColor(
`hsl(${this.hue},${this.saturation}%,${this.lightness}%)`,
).rgb;
return rgbaToHex(r, g, b, this.alpha);
}
hexNoAlpha(): string {
const [r, g, b] = parseColor(
`hsl(${this.hue},${this.saturation}%,${this.lightness}%)`,
).rgb;
return rgbaToHexNoAlpha(r, g, b);
}
private _lighten(mod: number): YaakColor {
const color = this.clone();
color.lightness = this.lightness + (100 - this.lightness) * mod;
return color;
}
private _darken(mod: number): YaakColor {
const color = this.clone();
color.lightness = this.lightness - this.lightness * mod;
return color;
}
}
function rgbaToHex(r: number, g: number, b: number, a: number): string {
const toHex = (n: number): string => {
const hex = Number(Math.round(n)).toString(16);
return hex.length === 1 ? `0${hex}` : hex;
};
return `#${[toHex(r), toHex(g), toHex(b), toHex(a * 255)].join("").toUpperCase()}`;
}
function rgbaToHexNoAlpha(r: number, g: number, b: number): string {
const toHex = (n: number): string => {
const hex = Number(Math.round(n)).toString(16);
return hex.length === 1 ? `0${hex}` : hex;
};
return `#${[toHex(r), toHex(g), toHex(b)].join("").toUpperCase()}`;
}
function hexToRgba(hex: string): [number, number, number, number] {
const fromHex = (value: string): number => {
if (value === "") return 255;
return Number(`0x${value}`);
};
const r = fromHex(hex.slice(1, 3));
const g = fromHex(hex.slice(3, 5));
const b = fromHex(hex.slice(5, 7));
const a = fromHex(hex.slice(7, 9));
return [r, g, b, a / 255];
}

View File

@@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "es2021",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"strict": true,
"module": "ESNext",
"moduleResolution": "Node",
"jsx": "react-jsx",
"noEmit": true
},
"include": ["src"]
}

16
packages/ui/package.json Normal file
View File

@@ -0,0 +1,16 @@
{
"name": "@yaakapp-internal/ui",
"private": true,
"version": "1.0.0",
"type": "module",
"main": "src/index.ts",
"types": "src/index.ts",
"dependencies": {
"react": "^19.1.0",
"react-dom": "^19.1.0"
},
"peerDependencies": {
"react": "^19.1.0",
"react-dom": "^19.1.0"
}
}

View File

@@ -0,0 +1,123 @@
import type { HTMLAttributes, ReactNode } from "react";
type ButtonColor =
| "default"
| "custom"
| "primary"
| "secondary"
| "info"
| "success"
| "notice"
| "warning"
| "danger";
type ButtonVariant = "border" | "solid";
type ButtonSize = "2xs" | "xs" | "sm" | "md" | "auto";
export type ButtonProps = Omit<
HTMLAttributes<HTMLButtonElement>,
"color" | "onChange"
> & {
innerClassName?: string;
color?: ButtonColor;
tone?: Exclude<ButtonColor, "custom">;
variant?: ButtonVariant;
isLoading?: boolean;
size?: ButtonSize;
justify?: "start" | "center";
type?: "button" | "submit";
disabled?: boolean;
title?: string;
leftSlot?: ReactNode;
rightSlot?: ReactNode;
};
function cx(...values: Array<string | false | null | undefined>) {
return values.filter(Boolean).join(" ");
}
export function Button({
isLoading,
className,
innerClassName,
children,
color,
tone,
type = "button",
justify = "center",
size = "md",
variant = "solid",
leftSlot,
rightSlot,
disabled,
title,
onClick,
...props
}: ButtonProps) {
const resolvedColor = color ?? tone ?? "default";
const isDisabled = disabled || isLoading;
return (
<button
type={type}
className={cx(
className,
"x-theme-button",
`x-theme-button--${variant}`,
`x-theme-button--${variant}--${resolvedColor}`,
"border",
"max-w-full min-w-0",
"hocus:opacity-100",
"whitespace-nowrap outline-none",
"flex-shrink-0 flex items-center",
"outline-0",
isDisabled ? "pointer-events-none opacity-disabled" : "pointer-events-auto",
justify === "start" && "justify-start",
justify === "center" && "justify-center",
size === "md" && "h-md px-3 rounded-md",
size === "sm" && "h-sm px-2.5 rounded-md",
size === "xs" && "h-xs px-2 text-sm rounded-md",
size === "2xs" && "h-2xs px-2 text-xs rounded",
size === "auto" && "px-3 py-2 rounded-md",
variant === "solid" && "border-transparent",
variant === "solid" &&
resolvedColor === "custom" &&
"focus-visible:outline-2 outline-border-focus",
variant === "solid" &&
resolvedColor !== "custom" &&
"text-text enabled:hocus:text-text enabled:hocus:bg-surface-highlight outline-border-subtle",
variant === "solid" &&
resolvedColor !== "custom" &&
resolvedColor !== "default" &&
"bg-surface",
variant === "border" && "border",
variant === "border" &&
resolvedColor !== "custom" &&
"border-border-subtle text-text-subtle enabled:hocus:border-border enabled:hocus:bg-surface-highlight enabled:hocus:text-text outline-border-subtler",
)}
disabled={isDisabled}
onClick={onClick}
onDoubleClick={(e) => {
e.stopPropagation();
}}
title={title}
{...props}
>
{isLoading ? (
<div className="mr-1">...</div>
) : leftSlot ? (
<div className="mr-2">{leftSlot}</div>
) : null}
<div
className={cx(
"truncate w-full",
justify === "start" ? "text-left" : "text-center",
innerClassName,
)}
>
{children}
</div>
{rightSlot ? <div className="ml-1">{rightSlot}</div> : null}
</button>
);
}

2
packages/ui/src/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export { Button } from "./components/Button";
export type { ButtonProps } from "./components/Button";

12
packages/ui/tsconfig.json Normal file
View File

@@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "es2021",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"strict": true,
"module": "ESNext",
"moduleResolution": "Node",
"jsx": "react-jsx",
"noEmit": true
},
"include": ["src"]
}