logancyang_obsidian-copilot/tailwind.config.js
Emt-lin 29293c14b1
feat(agent-home): segmented-tab shelf, aligned rows, top-anchored composer (#2550)
* feat(agent-home): brand icon, rotating greeting, and collapsible landing sections

- Add CopilotBrandIcon (inline brand SVG) beside the landing title; new
  ui-title font token sizes the heading off the Obsidian UI font, and register
  text-ui-title in the tailwind-merge font-size group so cn() dedupes it.
- Rotate the landing title through a curated greeting pool (landingGreetings),
  re-rolled per session so each new chat opens with a fresh line.
- Make AgentHomeSection collapsible (local state, chevron toggle, keyboard
  support, aria-controls). The disclosure and the header action sit as siblings
  to avoid nesting interactive controls, and the body's children unmount on
  collapse so an open "View all" popover tears down with it. Enable on Projects
  and Recent Chats.
- Resolve the landing subtitle's mode label through getModeLabel so it matches
  the ModePicker trigger (default -> Safe) from one source.

* fix(agent-home): keep landing scrollable on very short panes

The landing column was overflow-hidden, so a pane too short to fit the
status/title/composer stack clipped them with no way to scroll back
(small Obsidian splits / popouts). Restore overflow-y-auto on the
landing branch: the flex spacers still fill the column when there's
room (no scrollbar), and the column scrolls only when the fixed
elements genuinely overflow.

* fix(agent-home): resolve UI font token across themes

Obsidian's stock 20px UI token is --font-ui-larger, but some themes
(e.g. Primary) define --font-ui-large instead and omit the stock one.
Chain both for ui-larger/ui-title with a literal 20px floor, so the
landing title renders at the intended size under either convention
instead of dropping to inherited sizing when one var is missing.

* feat(agent-home): polish landing — colored project tiles, uppercase section headers, tighter spacing

- Section headers (Projects / Recent Chats): uppercase + tracking, drop the
  parens around the count, remove the leading section icon (keep the collapse
  chevron) since rows now carry their own leading marks.
- Project rows: stable per-id colored folder tile (tinted square + solid-hue
  folder) via new theme-aware `text/bg.project.*` tailwind tokens; hashed from
  the project id so the color is fixed per project.
- Landing: drop the redundant backend/mode subtitle (the composer pills already
  show it); retune spacing (heading→input, input→projects, between sections).
- Add-context "+" button: drop the border, bare glyph.
- "View all" project/chat links now use the theme accent color.

* docs(agent-home): refresh AgentHome section comments after icon removal

Drop stale references to the removed section icon in AgentHomeSection's
doc comments (section no longer supplies a type icon; rows carry their own
leading mark via `leading`/`icon`), and clarify the Recent Chats "View all"
trigger's pl-6 vs the Projects list's pl-8 alignment.

* refactor(agent-home): render brand icon from shared glyph data

Replace the dangerouslySetInnerHTML injection in CopilotBrandIcon with
plain JSX. Extract the glyph transform and path into COPILOT_AGENT_ICON_TRANSFORM
and COPILOT_AGENT_ICON_PATH constants; the addIcon string is now built from
the same primitives, so the React surface and the native Obsidian icon share
one source of truth and cannot drift.

* fix(agent-home): drop redundant count from "View all" links

The section header already shows the total (PROJECTS 10 / RECENT CHATS 582),
so repeating it in the "View all projects (N)" / "View all chats (N)" trigger
was duplicated. Trim both to "View all projects" / "View all chats".

* feat(agent-home): chip shelf landing — collapse sections into a covering panel

Replace the two always-open landing sections with a Claude-style chip shelf:
- Collapsed, Projects / Recent Chats are a centered row of pill chips (icon +
  title + count), built on the shared Button.
- Opening a chip *covers* the row with that section's panel — a titled card
  (icon + title + close ✕) over the section body; only one is ever open.
- Click anywhere outside the open panel collapses it (bound to the panel's
  ownerDocument for popout windows; Radix "View all" popovers are exempt).
- Projects' "New project" action moves to the panel's top-right, under the ✕.

AgentHomeSection loses its header component (title/count/collapse now live in the
chip); ProjectPickerList / GlobalRecentChatsSection render plain panel bodies and
keep the colored tiles, accent "View all", and count-free trigger.

* feat(agent-home): segmented-tab shelf, aligned rows, top-anchored composer

Turn the landing's Projects / Recent Chats into a persistent segmented-tab
card and polish the surrounding composer behaviour:

- Rename AgentHomeChip -> AgentHomeTab. Tabs use the ghost2 Button variant
  so Obsidian's native button chrome doesn't bleed through; selection is
  shown by background fill only (no border/shadow).
- Shelf card: one outer border + rounded-md. The panel is the scroll
  container via a min-h-0 chain, so on a short pane the card caps to the
  available height and its list scrolls internally while the tab bar stays
  pinned, instead of being clipped out of reach. An inner min-h reserves a
  consistent height across tabs.
- Recent-chat rows get a size-6 neutral icon tile matching the project
  tiles, so the leading slot is the same width and labels line up when
  switching tabs.
- Landing composer is top-anchored: a fixed-fraction (h-1/4) top spacer
  that doesn't absorb the composer's height changes, plus a flex-1 shelf
  region that does. Removing the active-note context chip now collapses the
  composer's lower half and the shelf upward, keeping the greeting fixed,
  instead of re-centering and dropping the top. The conversation state is
  unchanged (composer stays docked at the bottom).

* docs(agent-home): drop PR1 milestone label from project-sort DESIGN NOTE

Codex flagged the (PR1) reference as a STYLE_GUIDE violation (no
milestone/plan-step refs in code). State the read-only invariant
directly instead. Pre-existing PR1 refs from the merged PR1 are
baseline and left untouched.
2026-06-06 13:01:24 -07:00

277 lines
10 KiB
JavaScript

// tailwind.config.js
import { colorOpacityPlugin } from "./src/lib/plugins/colorOpacityPlugin";
import colors from "tailwindcss/colors";
import containerQueries from "@tailwindcss/container-queries";
/** @type {import("tailwindcss").Config} */
module.exports = {
prefix: "tw-",
content: ["./src/**/*.{js,ts,jsx,tsx}", "./src/styles/tailwind.css"],
darkMode: ["class"],
// tailwindcss-animate doesn't work with linter when using import statement.
// eslint-disable-next-line @typescript-eslint/no-require-imports
plugins: [require("tailwindcss-animate"), colorOpacityPlugin, containerQueries],
corePlugins: {
preflight: false,
},
// https://github.com/tailwindlabs/tailwindcss/blob/main/stubs/config.full.js
theme: {
textColor: {
inherit: colors.inherit,
current: colors.current,
transparent: colors.transparent,
normal: "var(--text-normal)",
muted: "var(--text-muted)",
faint: "var(--text-faint)",
"on-accent": "var(--text-on-accent)",
"on-accent-inverted": "var(--text-on-accent-inverted)",
success: "var(--text-success)",
warning: "var(--text-warning)",
loading: "var(--color-blue)",
error: "var(--text-error)",
accent: "var(--text-accent)",
"accent-hover": "var(--text-accent-hover)",
selection: "var(--text-selection)",
"highlight-bg": "var(--text-highlight-bg)",
callout: {
warning: "rgba(var(--callout-warning),<alpha-value>)",
},
"model-capabilities": {
green: "var(--color-green)",
blue: "var(--color-blue)",
},
"context-manager": {
blue: "var(--color-blue)",
green: "var(--color-green)",
red: "var(--color-red)",
yellow: "var(--color-yellow)",
orange: "var(--color-orange)",
purple: "var(--color-purple)",
},
// Per-project accent hues (Agent Home project tiles). Theme-aware via
// Obsidian's named color vars; paired with the matching `bg.project` tints.
project: {
red: "var(--color-red)",
orange: "var(--color-orange)",
yellow: "var(--color-yellow)",
green: "var(--color-green)",
blue: "var(--color-blue)",
purple: "var(--color-purple)",
},
},
backgroundColor: {
inherit: colors.inherit,
current: colors.current,
transparent: colors.transparent,
primary: "var(--background-primary)",
"primary-alt": "var(--background-primary-alt)",
secondary: "var(--background-secondary)",
"secondary-alt": "var(--background-secondary-alt)",
success: "rgba(var(--color-green-rgb),0.2)",
error: "rgba(var(--color-red-rgb),0.2)",
// Soft tints behind Agent Home project tiles; deeper `text.project` hue
// sits on top. Same six hues, kept in sync with `textColor.project`.
project: {
red: "rgba(var(--color-red-rgb),0.16)",
orange: "rgba(var(--color-orange-rgb),0.16)",
yellow: "rgba(var(--color-yellow-rgb),0.16)",
green: "rgba(var(--color-green-rgb),0.16)",
blue: "rgba(var(--color-blue-rgb),0.16)",
purple: "rgba(var(--color-purple-rgb),0.16)",
},
modifier: {
hover: "var(--background-modifier-hover)",
"active-hover": "var(--background-modifier-active-hover)",
error: "var(--background-modifier-error)",
"error-rgb": "rgba(var(--background-modifier-error-rgb),<alpha-value>)",
"error-hover": "var(--background-modifier-error-hover)",
success: "var(--background-modifier-success)",
"success-rgb": "rgba(var(--background-modifier-success-rgb),<alpha-value>)",
message: "var(--background-modifier-message)",
"form-field": "var(--background-form-field)",
},
interactive: {
normal: "var(--interactive-normal)",
hover: "var(--interactive-hover)",
accent: "var(--interactive-accent)",
"accent-hsl": "hsl(var(--interactive-accent-hsl),<alpha-value>)",
"accent-hover": "var(--interactive-accent-hover)",
},
dropdown: {
DEFAULT: "var(--dropdown-background)",
blend: "var(--dropdown-background-blend-mode)",
hover: "var(--dropdown-background-hover)",
},
callout: {
warning: "rgba(var(--callout-warning),<alpha-value>)",
},
overlay: {
DEFAULT: "#000",
},
toggle: {
thumb: "var(--toggle-thumb-color)",
},
},
borderColor: {
inherit: colors.inherit,
current: colors.current,
transparent: colors.transparent,
border: "var(--background-modifier-border)",
"border-hover": "var(--background-modifier-border-hover)",
"border-focus": "var(--background-modifier-border-focus)",
"interactive-accent": "var(--interactive-accent)",
},
ringColor: {
ring: "var(--interactive-accent)",
},
ringOffsetColor: {
ring: "var(--interactive-accent)",
},
colors: {
inherit: colors.inherit,
current: colors.current,
transparent: colors.transparent,
// preDefine CSS variables in Obsidian.(https://docs.obsidian.md/Reference/CSS+variables/Foundations/Colors)
base: {
"00": "var(--color-base-00)",
"05": "var(--color-base-05)",
10: "var(--color-base-10)",
20: "var(--color-base-20)",
25: "var(--color-base-25)",
30: "var(--color-base-30)",
35: "var(--color-base-35)",
40: "var(--color-base-40)",
50: "var(--color-base-50)",
60: "var(--color-base-60)",
70: "var(--color-base-70)",
100: "var(--color-base-100)",
},
red: "var(--color-red)",
"red-rgb": "rgba(var(--color-red-rgb),<alpha-value>)",
orange: "var(--color-orange)",
"orange-rgb": "rgba(var(--color-orange-rgb),<alpha-value>)",
yellow: "var(--color-yellow)",
"yellow-rgb": "rgba(var(--color-yellow-rgb),<alpha-value>)",
green: "var(--color-green)",
"green-rgb": "rgba(var(--color-green-rgb),<alpha-value>)",
cyan: "var(--color-cyan)",
"cyan-rgb": "rgba(var(--color-cyan-rgb),<alpha-value>)",
blue: "var(--color-blue)",
"blue-rgb": "rgba(var(--color-blue-rgb),<alpha-value>)",
purple: "var(--color-purple)",
"purple-rgb": "rgba(var(--color-purple-rgb),<alpha-value>)",
pink: "var(--color-pink)",
"pink-rgb": "rgba(var(--color-pink-rgb),<alpha-value>)",
gray: "var(--color-gray)",
"mono-rgb": {
0: "rgba(var(--mono-rgb-0),<alpha-value>)",
100: "rgba(var(--mono-rgb-100),<alpha-value>)",
},
caret: "var(--caret-color)",
icon: {
DEFAULT: "var(--icon-color)",
hover: "var(--icon-color-hover)",
active: "var(--icon-color-active)",
focused: "var(--icon-color-focused)",
},
},
borderWidth: {
DEFAULT: "var(--border-width)",
},
zIndex: {
cover: "var(--layer-cover)", // 5
sidedock: "var(--layer-sidedock)", // 10
"status-bar": "var(--layer-status-bar)", // 15
popover: "var(--layer-popover)", // 30
slides: "var(--layer-slides)", // 45
modal: "var(--layer-modal)", // 50
notice: "var(--layer-notice)", // 60
menu: "var(--layer-menu)", // 65
tooltip: "var(--layer-tooltip)", // 70
"dragged-item": "var(--layer-dragged-item)", // 80
},
fontWeight: {
thin: "var(--font-thin)", // 100
extralight: "var(--font-extralight)", // 200
light: "var(--font-light)", // 300
normal: "var(--font-normal)", // 400
medium: "var(--font-medium)", // 500
semibold: "var(--font-semibold)", // 600
bold: "var(--font-bold)", // 700
extrabold: "var(--font-extrabold)", // 800
black: "var(--font-black)", // 900
},
extend: {
borderRadius: {
"clickable-icon": "var(--clickable-icon-radius)",
xl: "var(--radius-xl)", // 16px
lg: "var(--radius-l)", // 12px
md: "var(--radius-m)", // 8px
sm: "var(--radius-s)", // 4px
},
spacing: {
0.5: "calc(var(--size-4-1) / 2)", // 2px
1: "var(--size-4-1)", // 4px
1.5: "calc(var(--size-4-1) * 1.5)", // 6px
2: "var(--size-4-2)",
3: "var(--size-4-3)",
4: "var(--size-4-4)",
5: "var(--size-4-5)",
5.5: "calc(var(--size-4-5) + 2px)",
6: "var(--size-4-6)",
},
cursor: {
DEFAULT: "var(--cursor)",
auto: "var(--cursor)",
pointer: "var(--cursor-link)",
},
fontSize: {
text: "var(--font-text-size)", // 16px
smallest: "var(--font-smallest)", // 0.8em
smaller: "var(--font-smaller)", // 0.875em
small: "var(--font-small)", // 0.933em
"ui-smaller": "var(--font-ui-smaller)", // 12px
"ui-small": "var(--font-ui-small)", // 13px
"ui-medium": "var(--font-ui-medium)", // 15px
// Obsidian's stock 20px UI token is `--font-ui-larger` (per the
// Typography docs), but some themes (e.g. Primary) ship `--font-ui-large`
// instead and omit the stock one. Chain both so the size resolves under
// either, falling back to a literal 20px if a theme defines neither
// (otherwise the font-size is invalid and the text drops to inherited).
"ui-larger": "var(--font-ui-large, var(--font-ui-larger, 20px))", // 20px
"ui-title": "var(--font-ui-large, var(--font-ui-larger, 20px))", // ~20px
},
strokeWidth: {
icon: "var(--icon-stroke)", // 1.75px
"icon-xs": "var(--icon-xs-stroke-width)", // 2px
"icon-s": "var(--icon-s-stroke-width)", // 2px
"icon-m": "var(--icon-m-stroke-width)", // 1.75px
"icon-l": "var(--icon-l-stroke-width)", // 1.75px
"icon-xl": "var(--icon-xl-stroke-width)", // 1.25px
},
lineHeight: {
normal: "var(--line-height-normal)", // 1.5
tight: "var(--line-height-tight)", // 1.3
},
size: {
icon: "var(--icon-size)", // 18px
"icon-xs": "var(--icon-xs)", // 14px
"icon-s": "var(--icon-s)", // 16px
"icon-m": "var(--icon-m)", // 18px
"icon-l": "var(--icon-l)", // 18px
"icon-xl": "var(--icon-xl)", // 32px
checkbox: "var(--checkbox-size)", //
},
opacity: {
icon: "var(--icon-opacity)", // 0.85
"icon-hover": "var(--icon-opacity-hover)", // 1
"icon-active": "var(--icon-opacity-active)", // 1
},
},
},
};