chore: run Tailwind v4 upgrade tool (Phase 1)
- Upgraded tailwindcss v3 → v4.2.1, postcss plugin to @tailwindcss/postcss - Deleted tailwind.config.js, migrated theme to CSS @theme block in index.css - Replaced @tailwind directives with @import 'tailwindcss' - Added @custom-variant dark, @utility blocks for custom utilities - Updated class names across 128 files (shadow-sm → shadow-xs, etc.) - Removed autoprefixer (built into v4) - Added migration plan doc Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
633
docs/tailwind-v4-migration.md
Normal file
633
docs/tailwind-v4-migration.md
Normal file
@@ -0,0 +1,633 @@
|
|||||||
|
# ResolutionFlow — Tailwind v4 Migration & Feature Guide
|
||||||
|
**Claude Code Handoff Document · Pre-Investor Pitch Sprint**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
This document gives Claude Code everything needed to:
|
||||||
|
1. Execute the mechanical Tailwind v3 → v4 upgrade
|
||||||
|
2. Know which new v4 features to use going forward so the ResolutionFlow UI looks and feels premium for the investor pitch demo
|
||||||
|
|
||||||
|
## Branch
|
||||||
|
|
||||||
|
This migration touches nearly every file in the frontend. **Always work on a dedicated branch:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout -b feat/tailwind-v4-upgrade
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not merge to `main` until all phases are complete and the full visual QA pass is done.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Stack Context
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| Frontend | React 19.2 + Vite 7 + TypeScript 5.9 |
|
||||||
|
| Current Tailwind | v3.4.19 |
|
||||||
|
| Component Library | shadcn/ui (target: new-york style post-upgrade) |
|
||||||
|
| Canvas Editor | React Flow (@xyflow/react) |
|
||||||
|
| Brand Color | Cyan — `#06b6d4 → #22d3ee` (hsl 187 72% 43%) |
|
||||||
|
| Background | `hsl(228 12% 7%)` — dark theme only, no light mode |
|
||||||
|
| Body Font | IBM Plex Sans |
|
||||||
|
| Heading Font | Bricolage Grotesque |
|
||||||
|
| Label/Mono Font | JetBrains Mono |
|
||||||
|
| Brand Gradient | `linear-gradient(135deg, #06b6d4 0%, #22d3ee 100%)` |
|
||||||
|
| Animation Library | tailwindcss-animate → **replace with tw-animate-css** |
|
||||||
|
| Tailwind Plugins | None |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# PART 1: Mechanical Migration
|
||||||
|
|
||||||
|
Execute phases in order. Verify the app builds and renders correctly after each phase before proceeding.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1 — Update Dependencies
|
||||||
|
|
||||||
|
Run the official Tailwind upgrade tool from the `/frontend` directory. It handles the majority of migration automatically:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run from /frontend
|
||||||
|
npx @tailwindcss/upgrade@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
This tool will:
|
||||||
|
- Update `tailwindcss` to v4
|
||||||
|
- Install `@tailwindcss/vite` (replaces the PostCSS plugin)
|
||||||
|
- Migrate `tailwind.config.js` → CSS `@theme` block
|
||||||
|
- Update `@tailwind` directives to `@import`
|
||||||
|
- Handle renamed utility classes in template files
|
||||||
|
|
||||||
|
After running, verify `package.json` reflects:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// devDependencies should now include:
|
||||||
|
"tailwindcss": "^4.x.x",
|
||||||
|
"@tailwindcss/vite": "^4.x.x"
|
||||||
|
|
||||||
|
// These are no longer needed and can be removed:
|
||||||
|
// "autoprefixer" (bundled in v4 via Lightning CSS)
|
||||||
|
// "postcss" (unless used for something other than Tailwind)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2 — Update Vite Config
|
||||||
|
|
||||||
|
Replace the PostCSS-based Tailwind setup with the first-party Vite plugin:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// vite.config.ts — BEFORE
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
})
|
||||||
|
|
||||||
|
// vite.config.ts — AFTER
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react(), tailwindcss()],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Remove `postcss.config.js` if it exists and was only used for Tailwind. Lightning CSS is now bundled inside Tailwind v4.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3 — Migrate CSS Entry Point
|
||||||
|
|
||||||
|
Update `index.css`:
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* BEFORE — v3 directives */
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
/* AFTER — v4 single import */
|
||||||
|
@import "tailwindcss";
|
||||||
|
@import "tw-animate-css";
|
||||||
|
|
||||||
|
/* ResolutionFlow is dark-only. Replace darkMode: ["class"] from tailwind.config.js
|
||||||
|
with this custom variant declaration. This is the v4 equivalent. */
|
||||||
|
@custom-variant dark (&:where(.dark, .dark *));
|
||||||
|
|
||||||
|
/* React Flow styles must now live in CSS, not imported in App.tsx */
|
||||||
|
@layer base {
|
||||||
|
@import "@xyflow/react/dist/style.css";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Important:** Remove `import '@xyflow/react/dist/style.css'` from `App.tsx` after adding it here.
|
||||||
|
> **Important:** Remove `darkMode: ["class"]` from `tailwind.config.js` — it is now handled by `@custom-variant dark` above. Since ResolutionFlow is dark-only with no light mode toggle, verify that the `.dark` class is still present on the `<html>` element in `index.html`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4 — Migrate Theme Configuration
|
||||||
|
|
||||||
|
The upgrade tool migrates `tailwind.config.js` automatically, but verify the ResolutionFlow design tokens are correctly expressed. The `@theme` block lives in `index.css` after the imports.
|
||||||
|
|
||||||
|
### OKLCH Color Values
|
||||||
|
|
||||||
|
Claude Code flagged that the cyan range benefits from OKLCH for better gradient interpolation. Use these OKLCH equivalents for brand colors inside `@theme` — they render more accurately on wide-gamut displays (MacBooks, modern monitors used in pitch settings):
|
||||||
|
|
||||||
|
| Hex | OKLCH |
|
||||||
|
|---|---|
|
||||||
|
| `#06b6d4` (brand-from) | `oklch(72% 0.15 195)` |
|
||||||
|
| `#22d3ee` (brand-to) | `oklch(82% 0.13 195)` |
|
||||||
|
| `#0891b2` (brand-dark) | `oklch(62% 0.14 195)` |
|
||||||
|
|
||||||
|
### Full `@theme` Block
|
||||||
|
|
||||||
|
```css
|
||||||
|
@import "tailwindcss";
|
||||||
|
@import "tw-animate-css";
|
||||||
|
|
||||||
|
@custom-variant dark (&:where(.dark, .dark *));
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
/* ResolutionFlow Brand — Cyan system (OKLCH for wide-gamut accuracy) */
|
||||||
|
--color-brand-from: oklch(72% 0.15 195); /* #06b6d4 */
|
||||||
|
--color-brand-to: oklch(82% 0.13 195); /* #22d3ee */
|
||||||
|
--color-brand-dark: oklch(62% 0.14 195); /* #0891b2 */
|
||||||
|
|
||||||
|
/* Dark surface palette */
|
||||||
|
--color-dark-DEFAULT: #101114;
|
||||||
|
--color-dark-card: #14161a;
|
||||||
|
--color-dark-surface: #14161a;
|
||||||
|
|
||||||
|
/* Text palette */
|
||||||
|
--color-text-primary: #f8fafc;
|
||||||
|
--color-text-secondary: #8891a0;
|
||||||
|
--color-text-muted: #5a6170;
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
--font-sans: 'IBM Plex Sans', system-ui, -apple-system, sans-serif;
|
||||||
|
--font-heading: 'Bricolage Grotesque', system-ui, sans-serif;
|
||||||
|
--font-label: 'JetBrains Mono', monospace;
|
||||||
|
|
||||||
|
/* Border radius */
|
||||||
|
--radius-lg: 0.75rem;
|
||||||
|
--radius-md: calc(0.75rem - 2px);
|
||||||
|
--radius-sm: calc(0.75rem - 4px);
|
||||||
|
|
||||||
|
/* Keyframe animations — move ALL @keyframes from index.css into @theme */
|
||||||
|
--animate-fade-in: fade-in 200ms ease-out;
|
||||||
|
--animate-fade-in-up: fade-in-up 200ms ease-out;
|
||||||
|
--animate-slide-in-left: slide-in-from-left 200ms ease-out;
|
||||||
|
--animate-slide-in-bottom: slide-in-from-bottom 200ms ease-out;
|
||||||
|
--animate-scale-in: scale-in 150ms ease-out;
|
||||||
|
--animate-breathe-glow: breatheGlow 3s ease-in-out infinite alternate;
|
||||||
|
--animate-bell-wobble: bellWobble 0.5s ease-in-out;
|
||||||
|
|
||||||
|
@keyframes fade-in {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
@keyframes fade-in-up {
|
||||||
|
from { opacity: 0; transform: translateY(4px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
@keyframes slide-in-from-left {
|
||||||
|
from { transform: translateX(-100%); }
|
||||||
|
to { transform: translateX(0); }
|
||||||
|
}
|
||||||
|
@keyframes slide-in-from-bottom {
|
||||||
|
from { opacity: 0; transform: translateY(16px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
@keyframes scale-in {
|
||||||
|
from { opacity: 0; transform: scale(0.95); }
|
||||||
|
to { opacity: 1; transform: scale(1); }
|
||||||
|
}
|
||||||
|
@keyframes breatheGlow {
|
||||||
|
from { box-shadow: 0 8px 32px rgba(0,0,0,0.3), 0 0 20px oklch(72% 0.15 195 / 0.04); }
|
||||||
|
to { box-shadow: 0 8px 32px rgba(0,0,0,0.3), 0 0 30px oklch(72% 0.15 195 / 0.12); }
|
||||||
|
}
|
||||||
|
@keyframes bellWobble {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
20% { transform: rotate(8deg); }
|
||||||
|
40% { transform: rotate(-6deg); }
|
||||||
|
60% { transform: rotate(4deg); }
|
||||||
|
80% { transform: rotate(-2deg); }
|
||||||
|
100% { transform: rotate(0deg); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Important:** Remove the loose `@keyframes` blocks from `index.css` after moving them into `@theme`. They should not exist in both places.
|
||||||
|
|
||||||
|
### shadcn/ui CSS Variable Bridge
|
||||||
|
|
||||||
|
Keep the existing `:root` block as-is — shadcn still uses HSL format for its variables:
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* shadcn/ui CSS variable bridge — keep as-is from existing index.css */
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--background: 228 12% 7%;
|
||||||
|
--foreground: 210 40% 98%;
|
||||||
|
--card: 220 10% 10%;
|
||||||
|
--card-foreground: 210 40% 98%;
|
||||||
|
--popover: 220 10% 10%;
|
||||||
|
--popover-foreground: 210 40% 98%;
|
||||||
|
--primary: 187 72% 43%;
|
||||||
|
--primary-foreground: 228 12% 7%;
|
||||||
|
--secondary: 220 8% 14%;
|
||||||
|
--secondary-foreground: 210 40% 98%;
|
||||||
|
--muted: 220 8% 14%;
|
||||||
|
--muted-foreground: 215 10% 58%;
|
||||||
|
--accent: 220 8% 14%;
|
||||||
|
--accent-foreground: 210 40% 98%;
|
||||||
|
--destructive: 350 81% 55%;
|
||||||
|
--destructive-foreground: 210 40% 98%;
|
||||||
|
--border: 220 8% 14%;
|
||||||
|
--input: 220 8% 14%;
|
||||||
|
--ring: 187 72% 43%;
|
||||||
|
--radius: 0.75rem;
|
||||||
|
/* ... rest of existing tokens ... */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5 — Replace Animation Library
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Remove old plugin
|
||||||
|
npm uninstall tailwindcss-animate
|
||||||
|
|
||||||
|
# Install replacement
|
||||||
|
npm install -D tw-animate-css
|
||||||
|
```
|
||||||
|
|
||||||
|
Remove any `@plugin 'tailwindcss-animate'` references from CSS files. The `@import "tw-animate-css"` added in Phase 3 replaces it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 7 — Consolidate Hardcoded Inline Colors
|
||||||
|
|
||||||
|
Claude Code identified **128 instances of `style={{}}` inline color values across 44 files**, including atmosphere orbs in `AppLayout.tsx` using hardcoded `rgba(6,182,212,...)`. Now that brand colors are proper CSS variables in `@theme`, replace all hardcoded cyan/brand references with variables.
|
||||||
|
|
||||||
|
**Search and replace targets:**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// FIND these hardcoded patterns:
|
||||||
|
rgba(6, 182, 212, ...) // brand-from with opacity
|
||||||
|
rgba(34, 211, 238, ...) // brand-to with opacity
|
||||||
|
#06b6d4 // brand-from hex
|
||||||
|
#22d3ee // brand-to hex
|
||||||
|
#0891b2 // brand-dark hex
|
||||||
|
|
||||||
|
// REPLACE with CSS variable equivalents:
|
||||||
|
oklch(72% 0.15 195 / 0.XX) // brand-from with opacity (XX = your alpha)
|
||||||
|
oklch(82% 0.13 195 / 0.XX) // brand-to with opacity
|
||||||
|
var(--color-brand-from) // brand-from solid
|
||||||
|
var(--color-brand-to) // brand-to solid
|
||||||
|
var(--color-brand-dark) // brand-dark solid
|
||||||
|
```
|
||||||
|
|
||||||
|
**Specifically in `AppLayout.tsx`** — atmosphere orbs should become:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// BEFORE
|
||||||
|
style={{ background: 'rgba(6, 182, 212, 0.08)' }}
|
||||||
|
|
||||||
|
// AFTER — references the theme token, respects any future brand color changes
|
||||||
|
style={{ background: 'oklch(72% 0.15 195 / 0.08)' }}
|
||||||
|
|
||||||
|
// Or better yet, move to a CSS utility class entirely:
|
||||||
|
// .atmosphere-orb { background: oklch(72% 0.15 195 / 0.08); }
|
||||||
|
```
|
||||||
|
|
||||||
|
> This is the right time to do this cleanup — the theme is already being touched, and it means future brand color changes only require updating `@theme` instead of hunting through 44 files.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 8 — Reinstall shadcn/ui Components
|
||||||
|
|
||||||
|
shadcn/ui components have been updated for Tailwind v4 and React 19. Commit any custom component modifications first, then reinstall to get refreshed versions.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Re-initialize with v4 defaults
|
||||||
|
npx shadcn@latest init
|
||||||
|
|
||||||
|
# When prompted:
|
||||||
|
# Style: new-york (more refined than 'default')
|
||||||
|
# Base color: cyan (matches ResolutionFlow brand)
|
||||||
|
|
||||||
|
# Reinstall actively used components:
|
||||||
|
npx shadcn@latest add button card dialog dropdown-menu
|
||||||
|
npx shadcn@latest add badge input label select textarea
|
||||||
|
npx shadcn@latest add table tabs tooltip separator
|
||||||
|
npx shadcn@latest add sheet command popover
|
||||||
|
```
|
||||||
|
|
||||||
|
**What's new in shadcn v4 components:**
|
||||||
|
- `forwardRef` removed from all components (React 19 handles ref natively)
|
||||||
|
- `data-slot` attributes added for easier CSS targeting
|
||||||
|
- HSL colors converted to OKLCH for wider color gamut
|
||||||
|
- Dark mode colors revisited for better accessibility
|
||||||
|
- `tailwindcss-animate` deprecated in favor of `tw-animate-css`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 9 — Visual QA Checklist
|
||||||
|
|
||||||
|
Work through the app after Phases 1–6. These are the most likely regression areas:
|
||||||
|
|
||||||
|
- [ ] **Borders** — default border color changed from `gray-200` to `currentColor`. Check all card, table, and input borders against the `hsl(var(--border))` token.
|
||||||
|
- [ ] **Focus rings** — default ring changed from `3px blue` to `1px currentColor`. Verify form fields and button focus states look intentional.
|
||||||
|
- [ ] **Placeholder text** — changed from `gray-400` to `currentColor` at 50% opacity. Check all inputs.
|
||||||
|
- [ ] **Button cursors** — now `cursor:default` (browser standard). Add `cursor-pointer` explicitly where desired.
|
||||||
|
- [ ] **React Flow canvas** — verify nodes, handles, edges, and controls render correctly with CSS import moved to `@layer base`.
|
||||||
|
- [ ] **Animations** — check dialog open/close, sheet slide, dropdown appear animations after the animation library swap.
|
||||||
|
- [ ] **Keyframe animations** — verify `breatheGlow` on stat cards, `bellWobble` on notification bell, and all `fade-in-*` utilities still work after moving keyframes into `@theme`.
|
||||||
|
- [ ] **Cyan brand gradient** — verify `bg-gradient-brand` still renders correctly. Check it looks at least as good (ideally better) with OKLCH values.
|
||||||
|
- [ ] **Atmosphere orbs** — verify `AppLayout.tsx` orbs render correctly after inline style replacement with OKLCH variables.
|
||||||
|
- [ ] **Glass morphism** — verify `.glass-card` and `.glass-card-static` backdrop blur still applies on all surfaces.
|
||||||
|
- [ ] **JetBrains Mono** — verify label/code elements still use mono font correctly.
|
||||||
|
- [ ] **Sonner toasts** — verify custom toast styling (card bg, border, icon colors) still applies.
|
||||||
|
- [ ] **Dark variant** — verify `.dark` class on `<html>` is present and the `@custom-variant dark` replacement behaves identically to the old `darkMode: ["class"]` config.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Commit Strategy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout -b feat/tailwind-v4-upgrade
|
||||||
|
|
||||||
|
git commit -m "chore: upgrade Tailwind v3 → v4, update Vite plugin"
|
||||||
|
git commit -m "chore: migrate CSS entry point — @import, @custom-variant dark"
|
||||||
|
git commit -m "chore: migrate theme to @theme block — OKLCH colors, keyframes"
|
||||||
|
git commit -m "chore: swap tailwindcss-animate for tw-animate-css"
|
||||||
|
git commit -m "chore: consolidate 128 hardcoded inline colors to CSS variables"
|
||||||
|
git commit -m "chore: reinstall shadcn/ui components for v4 (new-york style, cyan)"
|
||||||
|
git commit -m "fix: visual QA pass — borders, rings, placeholders, glass, animations"
|
||||||
|
git commit -m "feat: apply v4 feature enhancements across components"
|
||||||
|
|
||||||
|
# When complete and QA passes:
|
||||||
|
git checkout main
|
||||||
|
git merge feat/tailwind-v4-upgrade
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# PART 2: Tailwind v4 Feature Guide
|
||||||
|
|
||||||
|
**These are new capabilities available after the upgrade. Apply them proactively when building or modifying components — do not default to v3 patterns when a v4 equivalent is listed here.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature 1 — Auto-Resizing Textareas (`field-sizing-content`)
|
||||||
|
|
||||||
|
Apply to every multi-line text input in the app. Engineers typing session notes and documentation should never fight a fixed-height box.
|
||||||
|
|
||||||
|
**Where to apply:** Session runner documentation field · step notes input · FlowPilot prompt input · any multi-line input in the flow editor
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// BEFORE — v3, fixed height or JS resize logic
|
||||||
|
<textarea
|
||||||
|
className="w-full resize-none h-32 rounded-md border p-3"
|
||||||
|
/>
|
||||||
|
|
||||||
|
// AFTER — v4, grows automatically as the engineer types
|
||||||
|
<textarea
|
||||||
|
className="w-full field-sizing-content min-h-[80px] max-h-[400px]
|
||||||
|
rounded-md border border-input bg-background p-3
|
||||||
|
text-sm focus-visible:ring-2 focus-visible:ring-ring"
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature 2 — CSS-Native Enter Animations (`@starting-style`)
|
||||||
|
|
||||||
|
Use for elements that appear in the session runner — step transitions, panel reveals, status banners. No JavaScript animation library needed for these patterns.
|
||||||
|
|
||||||
|
**Where to apply:** Step cards appearing in session runner · status/alert banners · completion state reveals · FlowPilot suggestion panels
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* index.css — define the utility */
|
||||||
|
@utility fade-in-up {
|
||||||
|
transition: opacity 300ms ease, transform 300ms ease;
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
|
||||||
|
@starting-style {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(8px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility scale-in {
|
||||||
|
transition: opacity 150ms ease, transform 150ms ease;
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
|
||||||
|
@starting-style {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.97);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Component usage — animates in on mount with no JS
|
||||||
|
<div className="fade-in-up">
|
||||||
|
<StepCard step={currentStep} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="scale-in">
|
||||||
|
<FlowPilotPanel />
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature 3 — Container Queries (`@container`)
|
||||||
|
|
||||||
|
Use for components that need to adapt based on where they are rendered — not the viewport. Critical for ResolutionFlow where the same component may appear in a sidebar, modal, or full-width panel.
|
||||||
|
|
||||||
|
**Where to apply:** StepCard (sidebar vs full-width) · FlowPilot panel (collapsed vs expanded) · documentation preview panel · step library items
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Wrap the parent with @container
|
||||||
|
<div className="@container w-full">
|
||||||
|
<StepCard step={step} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
// Inside StepCard — responds to its container, not the viewport
|
||||||
|
function StepCard({ step }: { step: Step }) {
|
||||||
|
return (
|
||||||
|
<div className="
|
||||||
|
flex flex-col gap-2
|
||||||
|
@sm:flex-row @sm:items-center
|
||||||
|
@lg:gap-4
|
||||||
|
p-4 rounded-lg border border-border bg-card
|
||||||
|
">
|
||||||
|
<StepIcon type={step.type} />
|
||||||
|
<StepContent step={step} />
|
||||||
|
<div className="@sm:ml-auto">
|
||||||
|
<StepActions step={step} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature 4 — Enhanced Gradient APIs
|
||||||
|
|
||||||
|
The cyan brand gradient is ResolutionFlow's primary visual identity. v4 unlocks radial gradients, angle control, and OKLCH interpolation for richer rendering.
|
||||||
|
|
||||||
|
**Where to apply:** Hero/header sections · card hover states · active step indicators · FlowPilot branding elements · progress indicators
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Linear — explicit angle control (v4)
|
||||||
|
<div className="bg-linear-135 from-[#06b6d4] to-[#22d3ee]">
|
||||||
|
|
||||||
|
// Radial — great for glow effects on active/highlighted elements (v4)
|
||||||
|
<div className="bg-radial from-[#06b6d4]/20 to-transparent">
|
||||||
|
|
||||||
|
// Radial with position — spotlight effect (v4)
|
||||||
|
<div className="bg-radial-[at_30%_50%] from-[#06b6d4]/15 via-[#22d3ee]/8 to-transparent">
|
||||||
|
|
||||||
|
// OKLCH interpolation — richer, more accurate gradient transition (v4)
|
||||||
|
<div className="bg-linear-to-r from-[#06b6d4] to-[#22d3ee] [color-interpolation-method:oklch]">
|
||||||
|
|
||||||
|
// Combine with glass card for the premium demo look
|
||||||
|
<div className="glass-card-static bg-radial-[at_top_left] from-[#06b6d4]/10 to-transparent">
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature 5 — Dynamic Utility Values
|
||||||
|
|
||||||
|
In v3, non-standard values required bracket notation. In v4 the scale is continuous — use values directly.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// v3 — arbitrary brackets for anything off-scale
|
||||||
|
<div className="w-[18px] h-[18px] z-[60] grid-cols-[repeat(7,1fr)]">
|
||||||
|
|
||||||
|
// v4 — direct values work without brackets
|
||||||
|
<div className="w-4.5 h-4.5 z-60 grid-cols-7">
|
||||||
|
|
||||||
|
// Grid columns support any number directly
|
||||||
|
<div className="grid grid-cols-7"> // 7-column grid, no config needed
|
||||||
|
<div className="grid grid-cols-15"> // 15-column, works out of the box
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature 6 — `not-*` Variant
|
||||||
|
|
||||||
|
Style elements only when they don't match a condition. Useful in the session runner step list.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Bottom border on all steps except the last
|
||||||
|
<div className="not-last:border-b border-border pb-4 mb-4">
|
||||||
|
<StepCard />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
// Dim inactive steps
|
||||||
|
<div className="not-[.active]:opacity-60 not-[.active]:hover:opacity-80 transition-opacity">
|
||||||
|
<StepCard />
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature 7 — Theme Tokens as Native CSS Variables
|
||||||
|
|
||||||
|
After the v4 migration, all `@theme` tokens are exposed as native CSS custom properties. This means you can access ResolutionFlow design tokens in TypeScript — useful for React Flow node styling and Recharts chart colors.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Access brand colors in JS/TS — no more hardcoded hex values
|
||||||
|
const style = getComputedStyle(document.documentElement)
|
||||||
|
const brandColor = style.getPropertyValue('--color-brand-from').trim() // '#06b6d4'
|
||||||
|
|
||||||
|
// Use directly in React Flow node styles
|
||||||
|
const nodeStyles = {
|
||||||
|
background: 'var(--color-dark-card)',
|
||||||
|
border: '1px solid var(--color-brand-from)',
|
||||||
|
boxShadow: '0 0 12px rgba(6, 182, 212, 0.15)',
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use in Recharts chartConfig — no more hsl() wrapper needed
|
||||||
|
const chartConfig = {
|
||||||
|
sessions: {
|
||||||
|
label: 'Sessions',
|
||||||
|
color: 'var(--color-brand-from)',
|
||||||
|
},
|
||||||
|
resolved: {
|
||||||
|
label: 'Resolved',
|
||||||
|
color: 'var(--color-brand-to)',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature 8 — Color Scheme Utilities
|
||||||
|
|
||||||
|
Control native browser UI element theming (scrollbars, form controls) to match the dark theme — previously required custom CSS.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Apply to the root html element in index.html or App.tsx
|
||||||
|
<html className="scheme-dark">
|
||||||
|
|
||||||
|
// This makes native browser scrollbars, select dropdowns,
|
||||||
|
// date inputs, and form controls render in dark mode automatically.
|
||||||
|
// ResolutionFlow is dark-only so this should be applied globally.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature 9 — `inert` Utility
|
||||||
|
|
||||||
|
Disable interaction on an entire subtree without JavaScript. Useful for locking the flow editor or step list during FlowPilot AI generation.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Disable the step list while FlowPilot is generating
|
||||||
|
<div className={isGenerating ? 'inert' : ''}>
|
||||||
|
<StepList steps={steps} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
// The inert attribute disables all pointer events, focus, and
|
||||||
|
// accessibility interaction on the entire subtree at once.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## General Rules for v4 Going Forward
|
||||||
|
|
||||||
|
1. **Use `field-sizing-content` on every `<textarea>`** — no exceptions
|
||||||
|
2. **Use `@starting-style` animations** instead of adding/removing CSS classes for enter effects
|
||||||
|
3. **Wrap repeated components in `@container`** when they appear in variable-width contexts
|
||||||
|
4. **Use `not-last:` instead of custom `:not(:last-child)` selectors** in lists
|
||||||
|
5. **Reference `var(--color-brand-from)` and `var(--color-brand-to)`** in JS instead of hardcoded `#06b6d4` or `rgba(6,182,212,...)`
|
||||||
|
6. **Use OKLCH for any new color values** — `oklch(72% 0.15 195)` not `#06b6d4`
|
||||||
|
7. **All new keyframe animations go inside `@theme`** — not loose in `index.css`
|
||||||
|
8. **Do not use `tailwindcss-animate`** — replaced by `tw-animate-css`
|
||||||
|
9. **Do not add `@tailwind base/components/utilities` directives** — replaced by `@import "tailwindcss"`
|
||||||
|
10. **Do not add `darkMode: ["class"]` to any config** — replaced by `@custom-variant dark`
|
||||||
|
11. **Prefer `bg-linear-135` syntax** over `bg-gradient-to-r` for the brand gradient — more explicit and v4-native
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*ResolutionFlow · Tailwind v4 Migration Doc · Pre-Investor Pitch Sprint*
|
||||||
1408
frontend/package-lock.json
generated
1408
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -43,6 +43,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.1",
|
||||||
|
"@tailwindcss/postcss": "^4.2.1",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/react": "^16.3.2",
|
"@testing-library/react": "^16.3.2",
|
||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
@@ -50,14 +51,13 @@
|
|||||||
"@types/react": "^19.2.5",
|
"@types/react": "^19.2.5",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^5.1.1",
|
"@vitejs/plugin-react": "^5.1.1",
|
||||||
"autoprefixer": "^10.4.23",
|
|
||||||
"eslint": "^9.39.1",
|
"eslint": "^9.39.1",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-react-refresh": "^0.4.24",
|
"eslint-plugin-react-refresh": "^0.4.24",
|
||||||
"globals": "^16.5.0",
|
"globals": "^16.5.0",
|
||||||
"jsdom": "^28.0.0",
|
"jsdom": "^28.0.0",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"tailwindcss": "^3.4.19",
|
"tailwindcss": "^4.2.1",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"typescript-eslint": "^8.46.4",
|
"typescript-eslint": "^8.46.4",
|
||||||
"vite": "^7.2.4",
|
"vite": "^7.2.4",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
export default {
|
export default {
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
'@tailwindcss/postcss': {},
|
||||||
autoprefixer: {},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Outlet } from 'react-router-dom'
|
|||||||
|
|
||||||
export function AccountLayout() {
|
export function AccountLayout() {
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto max-w-screen-lg px-4 py-6">
|
<div className="container mx-auto max-w-(--breakpoint-lg) px-4 py-6">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export function DeleteAccountModal({ onClose }: Props) {
|
|||||||
required
|
required
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-[10px] border border-border bg-card px-3 py-2',
|
'mt-1 block w-full rounded-[10px] border border-border bg-card px-3 py-2',
|
||||||
'text-foreground focus:border-primary focus:outline-none'
|
'text-foreground focus:border-primary focus:outline-hidden'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -69,7 +69,7 @@ export function DeleteAccountModal({ onClose }: Props) {
|
|||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-[10px] px-4 py-2 text-sm font-medium',
|
'rounded-[10px] px-4 py-2 text-sm font-medium',
|
||||||
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground'
|
'bg-[rgba(255,255,255,0.04)] border border-brand-border text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export function LeaveAccountModal({ accountName, onClose }: Props) {
|
|||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-[10px] px-4 py-2 text-sm font-medium',
|
'rounded-[10px] px-4 py-2 text-sm font-medium',
|
||||||
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground'
|
'bg-[rgba(255,255,255,0.04)] border border-brand-border text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export function TransferOwnershipModal({ members, onClose, onTransferred }: Prop
|
|||||||
onChange={(e) => setTargetUserId(e.target.value)}
|
onChange={(e) => setTargetUserId(e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-[10px] border border-border bg-card px-3 py-2',
|
'mt-1 block w-full rounded-[10px] border border-border bg-card px-3 py-2',
|
||||||
'text-foreground focus:border-primary focus:outline-none'
|
'text-foreground focus:border-primary focus:outline-hidden'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{nonOwnerMembers.map((m) => (
|
{nonOwnerMembers.map((m) => (
|
||||||
@@ -77,7 +77,7 @@ export function TransferOwnershipModal({ members, onClose, onTransferred }: Prop
|
|||||||
required
|
required
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-[10px] border border-border bg-card px-3 py-2',
|
'mt-1 block w-full rounded-[10px] border border-border bg-card px-3 py-2',
|
||||||
'text-foreground focus:border-primary focus:outline-none'
|
'text-foreground focus:border-primary focus:outline-hidden'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -90,7 +90,7 @@ export function TransferOwnershipModal({ members, onClose, onTransferred }: Prop
|
|||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-[10px] px-4 py-2 text-sm font-medium',
|
'rounded-[10px] px-4 py-2 text-sm font-medium',
|
||||||
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground'
|
'bg-[rgba(255,255,255,0.04)] border border-brand-border text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
@@ -100,7 +100,7 @@ export function TransferOwnershipModal({ members, onClose, onTransferred }: Prop
|
|||||||
disabled={isSubmitting || !password}
|
disabled={isSubmitting || !password}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-[10px] px-4 py-2 text-sm font-semibold',
|
'rounded-[10px] px-4 py-2 text-sm font-semibold',
|
||||||
'bg-amber-500 text-[#101114] hover:bg-amber-400',
|
'bg-amber-500 text-brand-dark hover:bg-amber-400',
|
||||||
'disabled:opacity-50'
|
'disabled:opacity-50'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export function AdminLayout() {
|
|||||||
return (
|
return (
|
||||||
<div className="flex h-full">
|
<div className="flex h-full">
|
||||||
{/* Desktop sidebar */}
|
{/* Desktop sidebar */}
|
||||||
<div className="hidden w-60 flex-shrink-0 border-r border-border bg-card md:block">
|
<div className="hidden w-60 shrink-0 border-r border-border bg-card md:block">
|
||||||
<AdminSidebar />
|
<AdminSidebar />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ export function AdminLayout() {
|
|||||||
{mobileOpen && (
|
{mobileOpen && (
|
||||||
<div className="fixed inset-0 z-40 md:hidden">
|
<div className="fixed inset-0 z-40 md:hidden">
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 bg-card/80 backdrop-blur-sm"
|
className="absolute inset-0 bg-card/80 backdrop-blur-xs"
|
||||||
onClick={() => setMobileOpen(false)}
|
onClick={() => setMobileOpen(false)}
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-y-0 left-0 w-60 border-r border-border bg-card shadow-xl">
|
<div className="absolute inset-y-0 left-0 w-60 border-r border-border bg-card shadow-xl">
|
||||||
@@ -63,7 +63,7 @@ export function AdminLayout() {
|
|||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="flex-1 overflow-y-auto">
|
<div className="flex-1 overflow-y-auto">
|
||||||
<div className="mx-auto max-w-screen-2xl p-6">
|
<div className="mx-auto max-w-(--breakpoint-2xl) p-6">
|
||||||
{/* Mobile menu button */}
|
{/* Mobile menu button */}
|
||||||
<button
|
<button
|
||||||
onClick={() => setMobileOpen(true)}
|
onClick={() => setMobileOpen(true)}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export function CreateCategoryModal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs">
|
||||||
<div className="w-full max-w-md bg-card border border-border rounded-xl p-6 shadow-lg">
|
<div className="w-full max-w-md bg-card border border-border rounded-xl p-6 shadow-lg">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<div className="mb-4 flex items-center justify-between">
|
||||||
@@ -98,7 +98,7 @@ export function CreateCategoryModal({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||||
'placeholder:text-muted-foreground',
|
'placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'disabled:opacity-50'
|
'disabled:opacity-50'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -122,7 +122,7 @@ export function CreateCategoryModal({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||||
'placeholder:text-muted-foreground',
|
'placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'disabled:opacity-50'
|
'disabled:opacity-50'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export function EditCategoryModal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs">
|
||||||
<div className="w-full max-w-md bg-card border border-border rounded-xl p-6 shadow-lg">
|
<div className="w-full max-w-md bg-card border border-border rounded-xl p-6 shadow-lg">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<div className="mb-4 flex items-center justify-between">
|
||||||
@@ -107,7 +107,7 @@ export function EditCategoryModal({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||||
'placeholder:text-muted-foreground',
|
'placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'disabled:opacity-50'
|
'disabled:opacity-50'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -131,7 +131,7 @@ export function EditCategoryModal({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||||
'placeholder:text-muted-foreground',
|
'placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'disabled:opacity-50'
|
'disabled:opacity-50'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export function SearchInput({ value = '', onSearch, placeholder = 'Search...', c
|
|||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-9 w-full rounded-md border border-border bg-card pl-9 pr-8 text-sm text-foreground',
|
'h-9 w-full rounded-md border border-border bg-card pl-9 pr-8 text-sm text-foreground',
|
||||||
'placeholder:text-muted-foreground focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/20'
|
'placeholder:text-muted-foreground focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{localValue && (
|
{localValue && (
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export function FlowAnalyticsPanel({ treeId }: FlowAnalyticsPanelProps) {
|
|||||||
<select
|
<select
|
||||||
value={period}
|
value={period}
|
||||||
onChange={(e) => setPeriod(e.target.value as AnalyticsPeriod)}
|
onChange={(e) => setPeriod(e.target.value as AnalyticsPeriod)}
|
||||||
className="rounded-lg border border-border bg-card px-3 py-1.5 text-sm text-foreground focus:outline-none focus:ring-1 focus:ring-ring"
|
className="rounded-lg border border-border bg-card px-3 py-1.5 text-sm text-foreground focus:outline-hidden focus:ring-1 focus:ring-ring"
|
||||||
>
|
>
|
||||||
{PERIOD_OPTIONS.map((opt) => (
|
{PERIOD_OPTIONS.map((opt) => (
|
||||||
<option key={opt.value} value={opt.value}>
|
<option key={opt.value} value={opt.value}>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export function ChatMessage({ role, content, suggestedFlows }: ChatMessageProps)
|
|||||||
className={`rounded-2xl px-4 py-3 text-[0.875rem] leading-relaxed ${
|
className={`rounded-2xl px-4 py-3 text-[0.875rem] leading-relaxed ${
|
||||||
role === 'user'
|
role === 'user'
|
||||||
? 'bg-primary/15 text-foreground'
|
? 'bg-primary/15 text-foreground'
|
||||||
: 'bg-[rgba(255,255,255,0.04)] text-foreground border border-[rgba(255,255,255,0.06)]'
|
: 'bg-[rgba(255,255,255,0.04)] text-foreground border border-brand-border'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<MarkdownContent content={content} className="text-[0.875rem] leading-relaxed" />
|
<MarkdownContent content={content} className="text-[0.875rem] leading-relaxed" />
|
||||||
@@ -38,7 +38,7 @@ export function ChatMessage({ role, content, suggestedFlows }: ChatMessageProps)
|
|||||||
{/* Suggested flows (assistant only) */}
|
{/* Suggested flows (assistant only) */}
|
||||||
{role === 'assistant' && suggestedFlows && suggestedFlows.length > 0 && (
|
{role === 'assistant' && suggestedFlows && suggestedFlows.length > 0 && (
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
|
||||||
Related Flows
|
Related Flows
|
||||||
</span>
|
</span>
|
||||||
{suggestedFlows.map(flow => (
|
{suggestedFlows.map(flow => (
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export function ChatSidebar({
|
|||||||
<div className="px-4 py-3 border-b shrink-0" style={{ borderColor: 'var(--glass-border)' }}>
|
<div className="px-4 py-3 border-b shrink-0" style={{ borderColor: 'var(--glass-border)' }}>
|
||||||
<button
|
<button
|
||||||
onClick={onNewChat}
|
onClick={onNewChat}
|
||||||
className="w-full flex items-center justify-center gap-2 bg-gradient-brand text-[#101114] font-semibold text-sm rounded-[10px] px-4 py-2.5 hover:opacity-90 active:scale-[0.97] transition-all"
|
className="w-full flex items-center justify-center gap-2 bg-gradient-brand text-brand-dark font-semibold text-sm rounded-[10px] px-4 py-2.5 hover:opacity-90 active:scale-[0.97] transition-all"
|
||||||
>
|
>
|
||||||
<Plus size={16} />
|
<Plus size={16} />
|
||||||
New Chat
|
New Chat
|
||||||
@@ -42,7 +42,7 @@ export function ChatSidebar({
|
|||||||
<div className="flex-1 overflow-y-auto py-2">
|
<div className="flex-1 overflow-y-auto py-2">
|
||||||
{pinnedChats.length > 0 && (
|
{pinnedChats.length > 0 && (
|
||||||
<div className="px-3 mb-1">
|
<div className="px-3 mb-1">
|
||||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
|
||||||
Pinned
|
Pinned
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ export function ConcludeSessionModal({
|
|||||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||||
{/* Backdrop */}
|
{/* Backdrop */}
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
|
className="absolute inset-0 bg-black/60 backdrop-blur-xs"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ export function ConcludeSessionModal({
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="p-2 rounded-lg hover:bg-[rgba(255,255,255,0.06)] text-muted-foreground hover:text-foreground transition-colors"
|
className="p-2 rounded-lg hover:bg-brand-border text-muted-foreground hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
<X size={18} />
|
<X size={18} />
|
||||||
</button>
|
</button>
|
||||||
@@ -188,7 +188,7 @@ export function ConcludeSessionModal({
|
|||||||
'w-8 h-px',
|
'w-8 h-px',
|
||||||
step === s || (i === 1 && step === 'summary') || (i === 2 && step === 'summary')
|
step === s || (i === 1 && step === 'summary') || (i === 2 && step === 'summary')
|
||||||
? 'bg-primary/40'
|
? 'bg-primary/40'
|
||||||
: 'bg-[rgba(255,255,255,0.06)]'
|
: 'bg-brand-border'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -196,10 +196,10 @@ export function ConcludeSessionModal({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'w-6 h-6 rounded-full flex items-center justify-center text-[0.6875rem] font-label font-medium transition-colors',
|
'w-6 h-6 rounded-full flex items-center justify-center text-[0.6875rem] font-label font-medium transition-colors',
|
||||||
step === s
|
step === s
|
||||||
? 'bg-gradient-brand text-[#101114]'
|
? 'bg-gradient-brand text-brand-dark'
|
||||||
: (i < ['select-outcome', 'add-notes', 'summary'].indexOf(step))
|
: (i < ['select-outcome', 'add-notes', 'summary'].indexOf(step))
|
||||||
? 'bg-primary/20 text-primary'
|
? 'bg-primary/20 text-primary'
|
||||||
: 'bg-[rgba(255,255,255,0.06)] text-muted-foreground'
|
: 'bg-brand-border text-muted-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{i + 1}
|
{i + 1}
|
||||||
@@ -233,7 +233,7 @@ export function ConcludeSessionModal({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'w-full flex items-center gap-4 p-4 rounded-xl border transition-all text-left',
|
'w-full flex items-center gap-4 p-4 rounded-xl border transition-all text-left',
|
||||||
'hover:scale-[1.01] active:scale-[0.99]',
|
'hover:scale-[1.01] active:scale-[0.99]',
|
||||||
'bg-[rgba(255,255,255,0.02)] border-[rgba(255,255,255,0.06)]',
|
'bg-[rgba(255,255,255,0.02)] border-brand-border',
|
||||||
'hover:border-[rgba(255,255,255,0.12)] hover:bg-[rgba(255,255,255,0.04)]'
|
'hover:border-[rgba(255,255,255,0.12)] hover:bg-[rgba(255,255,255,0.04)]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -268,7 +268,7 @@ export function ConcludeSessionModal({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground block mb-2">
|
<label className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground block mb-2">
|
||||||
Additional Notes (optional)
|
Additional Notes (optional)
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
@@ -282,7 +282,7 @@ export function ConcludeSessionModal({
|
|||||||
: 'What still needs to be done, where you left off...'
|
: 'What still needs to be done, where you left off...'
|
||||||
}
|
}
|
||||||
rows={4}
|
rows={4}
|
||||||
className="w-full resize-none rounded-xl border bg-card text-foreground text-sm placeholder:text-muted-foreground px-4 py-3 focus:outline-none focus:border-[rgba(6,182,212,0.3)]"
|
className="w-full resize-none rounded-xl border bg-card text-foreground text-sm placeholder:text-muted-foreground px-4 py-3 focus:outline-hidden focus:border-[rgba(6,182,212,0.3)]"
|
||||||
style={{ borderColor: 'var(--glass-border)' }}
|
style={{ borderColor: 'var(--glass-border)' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -312,7 +312,7 @@ export function ConcludeSessionModal({
|
|||||||
style={{ borderColor: 'var(--glass-border)' }}
|
style={{ borderColor: 'var(--glass-border)' }}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground flex items-center gap-1.5">
|
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground flex items-center gap-1.5">
|
||||||
<Sparkles size={10} className="text-primary" />
|
<Sparkles size={10} className="text-primary" />
|
||||||
Generated Ticket Notes
|
Generated Ticket Notes
|
||||||
</span>
|
</span>
|
||||||
@@ -335,7 +335,7 @@ export function ConcludeSessionModal({
|
|||||||
<div />
|
<div />
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="px-4 py-2 rounded-[10px] text-sm text-muted-foreground hover:text-foreground bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] hover:border-[rgba(255,255,255,0.12)] transition-all"
|
className="px-4 py-2 rounded-[10px] text-sm text-muted-foreground hover:text-foreground bg-[rgba(255,255,255,0.04)] border border-brand-border hover:border-[rgba(255,255,255,0.12)] transition-all"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
@@ -346,14 +346,14 @@ export function ConcludeSessionModal({
|
|||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={() => setStep('select-outcome')}
|
onClick={() => setStep('select-outcome')}
|
||||||
className="px-4 py-2 rounded-[10px] text-sm text-muted-foreground hover:text-foreground bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] hover:border-[rgba(255,255,255,0.12)] transition-all"
|
className="px-4 py-2 rounded-[10px] text-sm text-muted-foreground hover:text-foreground bg-[rgba(255,255,255,0.04)] border border-brand-border hover:border-[rgba(255,255,255,0.12)] transition-all"
|
||||||
>
|
>
|
||||||
Back
|
Back
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleGenerate}
|
onClick={handleGenerate}
|
||||||
disabled={generating}
|
disabled={generating}
|
||||||
className="flex items-center gap-2 bg-gradient-brand text-[#101114] font-semibold text-sm rounded-[10px] px-5 py-2.5 hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-50"
|
className="flex items-center gap-2 bg-gradient-brand text-brand-dark font-semibold text-sm rounded-[10px] px-5 py-2.5 hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{generating ? (
|
{generating ? (
|
||||||
<>
|
<>
|
||||||
@@ -390,7 +390,7 @@ export function ConcludeSessionModal({
|
|||||||
'flex items-center gap-2 px-4 py-2.5 rounded-[10px] text-sm font-semibold transition-all',
|
'flex items-center gap-2 px-4 py-2.5 rounded-[10px] text-sm font-semibold transition-all',
|
||||||
copied
|
copied
|
||||||
? 'bg-emerald-400/15 text-emerald-400 border border-emerald-400/30'
|
? 'bg-emerald-400/15 text-emerald-400 border border-emerald-400/30'
|
||||||
: 'bg-gradient-brand text-[#101114] hover:opacity-90 active:scale-[0.97]'
|
: 'bg-gradient-brand text-brand-dark hover:opacity-90 active:scale-[0.97]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{copied ? (
|
{copied ? (
|
||||||
@@ -407,7 +407,7 @@ export function ConcludeSessionModal({
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="px-4 py-2.5 rounded-[10px] text-sm text-muted-foreground hover:text-foreground bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] hover:border-[rgba(255,255,255,0.12)] transition-all"
|
className="px-4 py-2.5 rounded-[10px] text-sm text-muted-foreground hover:text-foreground bg-[rgba(255,255,255,0.04)] border border-brand-border hover:border-[rgba(255,255,255,0.12)] transition-all"
|
||||||
>
|
>
|
||||||
Done
|
Done
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export function ContextMenu({ position, items, onClose }: ContextMenuProps) {
|
|||||||
'flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm transition-colors',
|
'flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm transition-colors',
|
||||||
item.variant === 'danger'
|
item.variant === 'danger'
|
||||||
? 'text-rose-400 hover:bg-rose-500/10'
|
? 'text-rose-400 hover:bg-rose-500/10'
|
||||||
: 'text-foreground hover:bg-[rgba(255,255,255,0.06)]'
|
: 'text-foreground hover:bg-brand-border'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{item.icon && (
|
{item.icon && (
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export function CreateFlowDropdown({
|
|||||||
{showMenu && (
|
{showMenu && (
|
||||||
<>
|
<>
|
||||||
<div className="fixed inset-0 z-10" onClick={() => setShowMenu(false)} />
|
<div className="fixed inset-0 z-10" onClick={() => setShowMenu(false)} />
|
||||||
<div className="absolute right-0 z-20 mt-1 w-64 rounded-lg border border-border bg-card p-1 shadow-xl backdrop-blur-sm">
|
<div className="absolute right-0 z-20 mt-1 w-64 rounded-lg border border-border bg-card p-1 shadow-xl backdrop-blur-xs">
|
||||||
{/* Troubleshooting */}
|
{/* Troubleshooting */}
|
||||||
<Link
|
<Link
|
||||||
to="/trees/new"
|
to="/trees/new"
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ export function Modal({ isOpen, onClose, title, children, footer, size = 'md', a
|
|||||||
>
|
>
|
||||||
{/* Backdrop */}
|
{/* Backdrop */}
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 bg-black/80 backdrop-blur-sm"
|
className="absolute inset-0 bg-black/80 backdrop-blur-xs"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
@@ -139,13 +139,13 @@ export function Modal({ isOpen, onClose, title, children, footer, size = 'md', a
|
|||||||
isFullScreen
|
isFullScreen
|
||||||
? 'fixed inset-4 max-w-none w-auto h-auto rounded-2xl'
|
? 'fixed inset-4 max-w-none w-auto h-auto rounded-2xl'
|
||||||
: cn(
|
: cn(
|
||||||
'max-h-[100vh] rounded-t-2xl sm:max-h-[85vh] sm:rounded-2xl',
|
'max-h-screen rounded-t-2xl sm:max-h-[85vh] sm:rounded-2xl',
|
||||||
sizeClasses[size]
|
sizeClasses[size]
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/* Header - Fixed at top */}
|
{/* Header - Fixed at top */}
|
||||||
<div className="flex flex-shrink-0 items-center justify-between border-b border-border px-4 py-3 sm:px-6 sm:py-4">
|
<div className="flex shrink-0 items-center justify-between border-b border-border px-4 py-3 sm:px-6 sm:py-4">
|
||||||
<h2 id="modal-title" className="text-lg font-semibold text-foreground">
|
<h2 id="modal-title" className="text-lg font-semibold text-foreground">
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
@@ -168,7 +168,7 @@ export function Modal({ isOpen, onClose, title, children, footer, size = 'md', a
|
|||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md p-1.5 text-muted-foreground transition-colors sm:p-1',
|
'rounded-md p-1.5 text-muted-foreground transition-colors sm:p-1',
|
||||||
'hover:bg-accent hover:text-foreground',
|
'hover:bg-accent hover:text-foreground',
|
||||||
'focus:outline-none focus:ring-2 focus:ring-primary/20'
|
'focus:outline-hidden focus:ring-2 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
aria-label="Close modal"
|
aria-label="Close modal"
|
||||||
>
|
>
|
||||||
@@ -184,7 +184,7 @@ export function Modal({ isOpen, onClose, title, children, footer, size = 'md', a
|
|||||||
|
|
||||||
{/* Footer - Fixed at bottom */}
|
{/* Footer - Fixed at bottom */}
|
||||||
{footer && (
|
{footer && (
|
||||||
<div className="flex-shrink-0 border-t border-border px-4 py-3 sm:px-6 sm:py-4">
|
<div className="shrink-0 border-t border-border px-4 py-3 sm:px-6 sm:py-4">
|
||||||
{footer}
|
{footer}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ export function TagInput({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex-1 min-w-[80px] border-0 bg-transparent px-1 py-0.5 text-sm text-foreground',
|
'flex-1 min-w-[80px] border-0 bg-transparent px-1 py-0.5 text-sm text-foreground',
|
||||||
'placeholder:text-muted-foreground',
|
'placeholder:text-muted-foreground',
|
||||||
'focus:outline-none focus:ring-0'
|
'focus:outline-hidden focus:ring-0'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="p-1.5 rounded-lg hover:bg-[rgba(255,255,255,0.06)] text-muted-foreground hover:text-foreground transition-colors"
|
className="p-1.5 rounded-lg hover:bg-brand-border text-muted-foreground hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
<X size={16} />
|
<X size={16} />
|
||||||
</button>
|
</button>
|
||||||
@@ -122,7 +122,7 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId
|
|||||||
className={`max-w-[85%] rounded-xl px-3.5 py-2.5 text-[0.8125rem] leading-relaxed ${
|
className={`max-w-[85%] rounded-xl px-3.5 py-2.5 text-[0.8125rem] leading-relaxed ${
|
||||||
msg.role === 'user'
|
msg.role === 'user'
|
||||||
? 'bg-primary/15 text-foreground'
|
? 'bg-primary/15 text-foreground'
|
||||||
: 'bg-[rgba(255,255,255,0.04)] text-foreground border border-[rgba(255,255,255,0.06)]'
|
: 'bg-[rgba(255,255,255,0.04)] text-foreground border border-brand-border'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<MarkdownContent content={msg.content} className="text-[0.8125rem] leading-relaxed" />
|
<MarkdownContent content={msg.content} className="text-[0.8125rem] leading-relaxed" />
|
||||||
@@ -131,7 +131,7 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId
|
|||||||
))}
|
))}
|
||||||
{loading && (
|
{loading && (
|
||||||
<div className="flex justify-start">
|
<div className="flex justify-start">
|
||||||
<div className="bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] rounded-xl px-3.5 py-2.5">
|
<div className="bg-[rgba(255,255,255,0.04)] border border-brand-border rounded-xl px-3.5 py-2.5">
|
||||||
<Loader2 size={16} className="animate-spin text-primary" />
|
<Loader2 size={16} className="animate-spin text-primary" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -140,7 +140,7 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId
|
|||||||
{/* Suggested flows */}
|
{/* Suggested flows */}
|
||||||
{suggestedFlows.length > 0 && (
|
{suggestedFlows.length > 0 && (
|
||||||
<div className="space-y-2 pt-2">
|
<div className="space-y-2 pt-2">
|
||||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
|
||||||
Related Flows
|
Related Flows
|
||||||
</span>
|
</span>
|
||||||
{suggestedFlows.map(flow => (
|
{suggestedFlows.map(flow => (
|
||||||
@@ -162,14 +162,14 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId
|
|||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
placeholder="Ask about this step..."
|
placeholder="Ask about this step..."
|
||||||
rows={1}
|
rows={1}
|
||||||
className="flex-1 resize-none rounded-xl border bg-card text-foreground text-[0.8125rem] placeholder:text-muted-foreground px-3.5 py-2.5 focus:outline-none focus:border-[rgba(6,182,212,0.3)]"
|
className="flex-1 resize-none rounded-xl border bg-card text-foreground text-[0.8125rem] placeholder:text-muted-foreground px-3.5 py-2.5 focus:outline-hidden focus:border-[rgba(6,182,212,0.3)]"
|
||||||
style={{ borderColor: 'var(--glass-border)' }}
|
style={{ borderColor: 'var(--glass-border)' }}
|
||||||
disabled={loading || initializing}
|
disabled={loading || initializing}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={handleSend}
|
onClick={handleSend}
|
||||||
disabled={!input.trim() || loading || initializing}
|
disabled={!input.trim() || loading || initializing}
|
||||||
className="bg-gradient-brand text-[#101114] p-2.5 rounded-xl hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-40"
|
className="bg-gradient-brand text-brand-dark p-2.5 rounded-xl hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-40"
|
||||||
>
|
>
|
||||||
<Send size={16} />
|
<Send size={16} />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export function CopilotToggle({ isOpen, onToggle }: CopilotToggleProps) {
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={onToggle}
|
onClick={onToggle}
|
||||||
className="fixed bottom-6 right-6 z-40 bg-gradient-brand text-[#101114] p-3.5 rounded-full shadow-lg shadow-primary/30 hover:opacity-90 active:scale-[0.97] transition-all"
|
className="fixed bottom-6 right-6 z-40 bg-gradient-brand text-brand-dark p-3.5 rounded-full shadow-lg shadow-primary/30 hover:opacity-90 active:scale-[0.97] transition-all"
|
||||||
title="Open AI Copilot"
|
title="Open AI Copilot"
|
||||||
>
|
>
|
||||||
<MessageCircle size={22} />
|
<MessageCircle size={22} />
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export function QuickStats({ stats }: QuickStatsProps) {
|
|||||||
className={cn('glass-card p-4 fade-in', i === 0 && 'active-glow')}
|
className={cn('glass-card p-4 fade-in', i === 0 && 'active-glow')}
|
||||||
style={{ animationDelay: `${50 + i * 30}ms` }}
|
style={{ animationDelay: `${50 + i * 30}ms` }}
|
||||||
>
|
>
|
||||||
<p className="font-label text-[0.625rem] font-medium uppercase tracking-[0.1em] text-muted-foreground">
|
<p className="font-label text-[0.625rem] font-medium uppercase tracking-widest text-muted-foreground">
|
||||||
{stat.label}
|
{stat.label}
|
||||||
</p>
|
</p>
|
||||||
<p
|
<p
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export function WeeklyCalendar({ events = {} }: WeeklyCalendarProps) {
|
|||||||
borderBottom: day.isToday ? '2px solid #06b6d4' : '1px solid var(--glass-border)',
|
borderBottom: day.isToday ? '2px solid #06b6d4' : '1px solid var(--glass-border)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className={`font-label text-[0.625rem] uppercase tracking-[0.1em] ${day.isToday ? 'text-cyan-400' : 'text-muted-foreground'}`}>
|
<span className={`font-label text-[0.625rem] uppercase tracking-widest ${day.isToday ? 'text-cyan-400' : 'text-muted-foreground'}`}>
|
||||||
{day.label}
|
{day.label}
|
||||||
</span>
|
</span>
|
||||||
<div className={`text-sm font-heading font-bold ${day.isToday ? 'text-foreground' : 'text-muted-foreground'}`}>
|
<div className={`text-sm font-heading font-bold ${day.isToday ? 'text-foreground' : 'text-muted-foreground'}`}>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export function AIPromptDialog({
|
|||||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||||
{/* Backdrop */}
|
{/* Backdrop */}
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
|
className="absolute inset-0 bg-black/60 backdrop-blur-xs"
|
||||||
onClick={() => !isGenerating && onClose()}
|
onClick={() => !isGenerating && onClose()}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ export function AIPromptDialog({
|
|||||||
placeholder={`Example: "A flow for troubleshooting VPN connectivity issues when users can't connect to the corporate network"`}
|
placeholder={`Example: "A flow for troubleshooting VPN connectivity issues when users can't connect to the corporate network"`}
|
||||||
rows={4}
|
rows={4}
|
||||||
disabled={isGenerating}
|
disabled={isGenerating}
|
||||||
className="w-full rounded-xl border border-border bg-background px-4 py-3 text-sm text-foreground placeholder:text-muted-foreground focus:border-[rgba(6,182,212,0.3)] focus:outline-none resize-none disabled:opacity-50"
|
className="w-full rounded-xl border border-border bg-background px-4 py-3 text-sm text-foreground placeholder:text-muted-foreground focus:border-[rgba(6,182,212,0.3)] focus:outline-hidden resize-none disabled:opacity-50"
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -83,14 +83,14 @@ export function AIPromptDialog({
|
|||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
disabled={isGenerating}
|
disabled={isGenerating}
|
||||||
className="rounded-[10px] bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] px-4 py-2 text-sm text-foreground hover:border-[rgba(255,255,255,0.12)] transition-colors disabled:opacity-50"
|
className="rounded-[10px] bg-[rgba(255,255,255,0.04)] border border-brand-border px-4 py-2 text-sm text-foreground hover:border-[rgba(255,255,255,0.12)] transition-colors disabled:opacity-50"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleGenerate}
|
onClick={handleGenerate}
|
||||||
disabled={!prompt.trim() || isGenerating}
|
disabled={!prompt.trim() || isGenerating}
|
||||||
className="flex items-center gap-2 rounded-[10px] bg-gradient-brand px-4 py-2 text-sm font-semibold text-[#101114] shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-50"
|
className="flex items-center gap-2 rounded-[10px] bg-gradient-brand px-4 py-2 text-sm font-semibold text-brand-dark shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{isGenerating ? (
|
{isGenerating ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export function ChatTab({ messages, input, onInputChange, onSend, isLoading }: C
|
|||||||
className={`max-w-[85%] rounded-xl px-3.5 py-2.5 text-[0.8125rem] leading-relaxed ${
|
className={`max-w-[85%] rounded-xl px-3.5 py-2.5 text-[0.8125rem] leading-relaxed ${
|
||||||
msg.role === 'user'
|
msg.role === 'user'
|
||||||
? 'bg-primary/15 text-foreground'
|
? 'bg-primary/15 text-foreground'
|
||||||
: 'bg-[rgba(255,255,255,0.04)] text-foreground border border-[rgba(255,255,255,0.06)]'
|
: 'bg-[rgba(255,255,255,0.04)] text-foreground border border-brand-border'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<MarkdownContent content={msg.content} className="text-[0.8125rem] leading-relaxed" />
|
<MarkdownContent content={msg.content} className="text-[0.8125rem] leading-relaxed" />
|
||||||
@@ -59,7 +59,7 @@ export function ChatTab({ messages, input, onInputChange, onSend, isLoading }: C
|
|||||||
))}
|
))}
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<div className="flex justify-start">
|
<div className="flex justify-start">
|
||||||
<div className="bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] rounded-xl px-3.5 py-2.5">
|
<div className="bg-[rgba(255,255,255,0.04)] border border-brand-border rounded-xl px-3.5 py-2.5">
|
||||||
<Loader2 size={16} className="animate-spin text-primary" />
|
<Loader2 size={16} className="animate-spin text-primary" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -77,14 +77,14 @@ export function ChatTab({ messages, input, onInputChange, onSend, isLoading }: C
|
|||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
placeholder="Ask AI to help..."
|
placeholder="Ask AI to help..."
|
||||||
rows={1}
|
rows={1}
|
||||||
className="flex-1 resize-none rounded-xl border bg-card text-foreground text-[0.8125rem] placeholder:text-muted-foreground px-3.5 py-2.5 focus:outline-none focus:border-[rgba(6,182,212,0.3)]"
|
className="flex-1 resize-none rounded-xl border bg-card text-foreground text-[0.8125rem] placeholder:text-muted-foreground px-3.5 py-2.5 focus:outline-hidden focus:border-[rgba(6,182,212,0.3)]"
|
||||||
style={{ borderColor: 'var(--glass-border)' }}
|
style={{ borderColor: 'var(--glass-border)' }}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={onSend}
|
onClick={onSend}
|
||||||
disabled={!input.trim() || isLoading}
|
disabled={!input.trim() || isLoading}
|
||||||
className="bg-gradient-brand text-[#101114] p-2.5 rounded-xl hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-40"
|
className="bg-gradient-brand text-brand-dark p-2.5 rounded-xl hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-40"
|
||||||
>
|
>
|
||||||
<Send size={16} />
|
<Send size={16} />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export function EditorAIPanel({
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="p-1.5 rounded-lg hover:bg-[rgba(255,255,255,0.06)] text-muted-foreground hover:text-foreground transition-colors"
|
className="p-1.5 rounded-lg hover:bg-brand-border text-muted-foreground hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
<X size={16} />
|
<X size={16} />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export function NodeSummary({ node, flowName, flowType, nodeCount }: NodeSummary
|
|||||||
{flowName || 'Untitled Flow'}
|
{flowName || 'Untitled Flow'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-1 flex items-center gap-3 font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
<div className="mt-1 flex items-center gap-3 font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
|
||||||
<span>{flowType || 'flow'}</span>
|
<span>{flowType || 'flow'}</span>
|
||||||
{nodeCount !== undefined && <span>{nodeCount} nodes</span>}
|
{nodeCount !== undefined && <span>{nodeCount} nodes</span>}
|
||||||
</div>
|
</div>
|
||||||
@@ -44,7 +44,7 @@ export function NodeSummary({ node, flowName, flowType, nodeCount }: NodeSummary
|
|||||||
<div className="border-b px-3 py-2.5" style={{ borderColor: 'var(--glass-border)' }}>
|
<div className="border-b px-3 py-2.5" style={{ borderColor: 'var(--glass-border)' }}>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Icon className={`h-3.5 w-3.5 ${colorClass}`} />
|
<Icon className={`h-3.5 w-3.5 ${colorClass}`} />
|
||||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
|
||||||
{node.type}
|
{node.type}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export function SuggestionsTab({ suggestions }: SuggestionsTabProps) {
|
|||||||
return (
|
return (
|
||||||
<div key={s.id} className="rounded-lg border border-border bg-[rgba(255,255,255,0.02)] px-3 py-2">
|
<div key={s.id} className="rounded-lg border border-border bg-[rgba(255,255,255,0.02)] px-3 py-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
|
||||||
{s.action_type.replace(/_/g, ' ')}
|
{s.action_type.replace(/_/g, ' ')}
|
||||||
</span>
|
</span>
|
||||||
<span className={`flex items-center gap-1 text-xs ${config.color}`}>
|
<span className={`flex items-center gap-1 text-xs ${config.color}`}>
|
||||||
@@ -39,7 +39,7 @@ export function SuggestionsTab({ suggestions }: SuggestionsTabProps) {
|
|||||||
{s.target_node_id && (
|
{s.target_node_id && (
|
||||||
<p className="mt-1 text-xs text-muted-foreground truncate">Node: {s.target_node_id}</p>
|
<p className="mt-1 text-xs text-muted-foreground truncate">Node: {s.target_node_id}</p>
|
||||||
)}
|
)}
|
||||||
<p className="mt-0.5 font-label text-[0.625rem] text-[#5a6170]">
|
<p className="mt-0.5 font-label text-[0.625rem] text-brand-text-muted">
|
||||||
{new Date(s.created_at).toLocaleDateString()}
|
{new Date(s.created_at).toLocaleDateString()}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export function GuideCard({ guide }: GuideCardProps) {
|
|||||||
<p className="text-xs text-muted-foreground leading-relaxed">
|
<p className="text-xs text-muted-foreground leading-relaxed">
|
||||||
{guide.summary}
|
{guide.summary}
|
||||||
</p>
|
</p>
|
||||||
<span className="mt-2 inline-block font-label text-[0.625rem] uppercase tracking-[0.1em] text-primary">
|
<span className="mt-2 inline-block font-label text-[0.625rem] uppercase tracking-widest text-primary">
|
||||||
{guide.sections.length} {guide.sections.length === 1 ? 'section' : 'sections'}
|
{guide.sections.length} {guide.sections.length === 1 ? 'section' : 'sections'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export function AppLayout() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={cn('app-shell relative z-[1]', sidebarCollapsed && 'app-shell--collapsed')}>
|
<div className={cn('app-shell relative z-1', sidebarCollapsed && 'app-shell--collapsed')}>
|
||||||
{/* Top Bar - spans full width */}
|
{/* Top Bar - spans full width */}
|
||||||
<TopBar />
|
<TopBar />
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ export function AppLayout() {
|
|||||||
{mobileMenuOpen && (
|
{mobileMenuOpen && (
|
||||||
<div className="fixed inset-0 z-50 md:hidden">
|
<div className="fixed inset-0 z-50 md:hidden">
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 bg-black/80 backdrop-blur-sm animate-fade-in"
|
className="absolute inset-0 bg-black/80 backdrop-blur-xs animate-fade-in"
|
||||||
onClick={() => setMobileMenuOpen(false)}
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -124,10 +124,10 @@ export function CommandPalette({ open, onClose }: CommandPaletteProps) {
|
|||||||
if (!open) return null
|
if (!open) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-[100] flex items-start justify-center pt-[20vh]">
|
<div className="fixed inset-0 z-100 flex items-start justify-center pt-[20vh]">
|
||||||
{/* Backdrop */}
|
{/* Backdrop */}
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 bg-black/60 backdrop-blur-sm animate-fade-in"
|
className="absolute inset-0 bg-black/60 backdrop-blur-xs animate-fade-in"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -143,7 +143,7 @@ export function CommandPalette({ open, onClose }: CommandPaletteProps) {
|
|||||||
onChange={e => { setQuery(e.target.value); setSelectedIndex(0) }}
|
onChange={e => { setQuery(e.target.value); setSelectedIndex(0) }}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
placeholder="Search flows, sessions…"
|
placeholder="Search flows, sessions…"
|
||||||
className="flex-1 bg-transparent text-sm text-foreground placeholder:text-muted-foreground outline-none"
|
className="flex-1 bg-transparent text-sm text-foreground placeholder:text-muted-foreground outline-hidden"
|
||||||
/>
|
/>
|
||||||
<kbd className="rounded border border-border bg-background px-1.5 py-0.5 font-label text-[0.625rem] text-muted-foreground">
|
<kbd className="rounded border border-border bg-background px-1.5 py-0.5 font-label text-[0.625rem] text-muted-foreground">
|
||||||
ESC
|
ESC
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export function EmailVerificationBanner() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-3 border-b border-amber-400/20 bg-amber-400/5 px-4 py-2 text-sm">
|
<div className="flex items-center gap-3 border-b border-amber-400/20 bg-amber-400/5 px-4 py-2 text-sm">
|
||||||
<AlertTriangle className="h-4 w-4 flex-shrink-0 text-amber-400" />
|
<AlertTriangle className="h-4 w-4 shrink-0 text-amber-400" />
|
||||||
<span className="text-amber-200">
|
<span className="text-amber-200">
|
||||||
Your email is not verified.
|
Your email is not verified.
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -70,8 +70,8 @@ export function QuickLaunch({ open, onClose }: QuickLaunchProps) {
|
|||||||
if (!open) return null
|
if (!open) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-[100] flex items-start justify-center pt-[15vh]">
|
<div className="fixed inset-0 z-100 flex items-start justify-center pt-[15vh]">
|
||||||
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm animate-fade-in" onClick={onClose} />
|
<div className="absolute inset-0 bg-black/60 backdrop-blur-xs animate-fade-in" onClick={onClose} />
|
||||||
<div ref={containerRef} className="relative w-full max-w-md rounded-xl border border-border bg-card shadow-2xl animate-scale-in">
|
<div ref={containerRef} className="relative w-full max-w-md rounded-xl border border-border bg-card shadow-2xl animate-scale-in">
|
||||||
<div className="flex items-center justify-between border-b border-border px-4 py-3">
|
<div className="flex items-center justify-between border-b border-border px-4 py-3">
|
||||||
<h3 className="text-sm font-heading font-semibold text-foreground">Quick Launch</h3>
|
<h3 className="text-sm font-heading font-semibold text-foreground">Quick Launch</h3>
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export function AddToFolderMenu({ treeId, onFolderCreated }: AddToFolderMenuProp
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'absolute right-0 top-full z-20 mt-1 w-48 rounded-md border border-border',
|
'absolute right-0 top-full z-20 mt-1 w-48 rounded-md border border-border',
|
||||||
'bg-card backdrop-blur-sm py-1 shadow-lg'
|
'bg-card backdrop-blur-xs py-1 shadow-lg'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ export function FolderEditModal({
|
|||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||||
{/* Backdrop */}
|
{/* Backdrop */}
|
||||||
<div className="absolute inset-0 bg-black/80 backdrop-blur-sm" onClick={onClose} />
|
<div className="absolute inset-0 bg-black/80 backdrop-blur-xs" onClick={onClose} />
|
||||||
|
|
||||||
{/* Modal */}
|
{/* Modal */}
|
||||||
<div className="relative z-10 w-full max-w-md bg-card border border-border rounded-2xl p-6 shadow-lg">
|
<div className="relative z-10 w-full max-w-md bg-card border border-border rounded-2xl p-6 shadow-lg">
|
||||||
@@ -202,7 +202,7 @@ export function FolderEditModal({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
||||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
'bg-card text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'border-border'
|
'border-border'
|
||||||
)}
|
)}
|
||||||
autoFocus
|
autoFocus
|
||||||
@@ -221,7 +221,7 @@ export function FolderEditModal({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
||||||
'bg-card text-foreground',
|
'bg-card text-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'border-border'
|
'border-border'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ function FolderItem({
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'absolute right-0 top-full z-10 mt-1 w-40 rounded-md border border-border',
|
'absolute right-0 top-full z-10 mt-1 w-40 rounded-md border border-border',
|
||||||
'bg-card backdrop-blur-sm py-1 shadow-lg'
|
'bg-card backdrop-blur-xs py-1 shadow-lg'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@@ -362,7 +362,7 @@ export function FolderSidebar({
|
|||||||
{/* Mobile backdrop */}
|
{/* Mobile backdrop */}
|
||||||
{mobileOpen && (
|
{mobileOpen && (
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 z-40 bg-black/80 backdrop-blur-sm md:hidden"
|
className="fixed inset-0 z-40 bg-black/80 backdrop-blur-xs md:hidden"
|
||||||
onClick={onMobileClose}
|
onClick={onMobileClose}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
@@ -461,7 +461,7 @@ export function FolderSidebar({
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'fixed z-50 w-44 rounded-md border border-border',
|
'fixed z-50 w-44 rounded-md border border-border',
|
||||||
'bg-card backdrop-blur-sm py-1 shadow-lg'
|
'bg-card backdrop-blur-xs py-1 shadow-lg'
|
||||||
)}
|
)}
|
||||||
style={{ left: contextMenu.x, top: contextMenu.y }}
|
style={{ left: contextMenu.x, top: contextMenu.y }}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export function ForkModal({ treeId, treeName, onClose }: ForkModalProps) {
|
|||||||
maxLength={255}
|
maxLength={255}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground',
|
'w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||||
'placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
|
'placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -105,7 +105,7 @@ export function ForkModal({ treeId, treeName, onClose }: ForkModalProps) {
|
|||||||
placeholder="e.g. customizing for a specific client…"
|
placeholder="e.g. customizing for a specific client…"
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full resize-none rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground',
|
'w-full resize-none rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||||
'placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
|
'placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ export function ImportFlowModal({ onClose }: ImportFlowModalProps) {
|
|||||||
/>
|
/>
|
||||||
{parseError && (
|
{parseError && (
|
||||||
<div className="flex items-start gap-2 rounded-lg border border-rose-500/20 bg-rose-500/5 px-3 py-2">
|
<div className="flex items-start gap-2 rounded-lg border border-rose-500/20 bg-rose-500/5 px-3 py-2">
|
||||||
<AlertTriangle className="mt-0.5 h-4 w-4 flex-shrink-0 text-rose-400" />
|
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-rose-400" />
|
||||||
<p className="text-xs text-rose-400">{parseError}</p>
|
<p className="text-xs text-rose-400">{parseError}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -165,7 +165,7 @@ export function ImportFlowModal({ onClose }: ImportFlowModalProps) {
|
|||||||
maxLength={255}
|
maxLength={255}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground',
|
'w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||||
'placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
|
'placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
|
|||||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||||
{/* Backdrop */}
|
{/* Backdrop */}
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 bg-black/80 backdrop-blur-sm"
|
className="absolute inset-0 bg-black/80 backdrop-blur-xs"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -221,7 +221,7 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
|
|||||||
type="text"
|
type="text"
|
||||||
value={activeShare.share_url}
|
value={activeShare.share_url}
|
||||||
readOnly
|
readOnly
|
||||||
className="flex-1 bg-transparent text-sm text-foreground outline-none"
|
className="flex-1 bg-transparent text-sm text-foreground outline-hidden"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={handleCopyLink}
|
onClick={handleCopyLink}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export function SortDropdown({ value, onChange, className }: SortDropdownProps)
|
|||||||
onChange={(e) => onChange(e.target.value as SortBy)}
|
onChange={(e) => onChange(e.target.value as SortBy)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border bg-card px-3 py-1.5 text-sm',
|
'rounded-md border border-border bg-card px-3 py-1.5 text-sm',
|
||||||
'text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
|
'text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{sortOptions.map((option) => (
|
{sortOptions.map((option) => (
|
||||||
|
|||||||
@@ -37,20 +37,20 @@ export function TreeListView({
|
|||||||
{trees.map((tree) => (
|
{trees.map((tree) => (
|
||||||
<div
|
<div
|
||||||
key={tree.id}
|
key={tree.id}
|
||||||
className="flex items-center gap-4 bg-card border border-border rounded-2xl p-4 transition-all hover:border-primary/30 hover:shadow-sm"
|
className="flex items-center gap-4 bg-card border border-border rounded-2xl p-4 transition-all hover:border-primary/30 hover:shadow-xs"
|
||||||
>
|
>
|
||||||
{/* Left: Name and Description */}
|
{/* Left: Name and Description */}
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
<h3 className="font-semibold text-foreground truncate">{tree.name}</h3>
|
<h3 className="font-semibold text-foreground truncate">{tree.name}</h3>
|
||||||
{tree.status === 'draft' && (
|
{tree.status === 'draft' && (
|
||||||
<span className="inline-flex items-center gap-1 rounded-full bg-yellow-400/10 px-2 py-0.5 text-xs font-medium text-yellow-400 flex-shrink-0">
|
<span className="inline-flex items-center gap-1 rounded-full bg-yellow-400/10 px-2 py-0.5 text-xs font-medium text-yellow-400 shrink-0">
|
||||||
<FileText className="h-3 w-3" />
|
<FileText className="h-3 w-3" />
|
||||||
Draft
|
Draft
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{tree.tree_type === 'maintenance' && (
|
{tree.tree_type === 'maintenance' && (
|
||||||
<span className="inline-flex items-center gap-1 rounded-full border border-amber-500/30 bg-amber-500/10 px-2 py-0.5 font-label text-[0.625rem] uppercase tracking-wide text-amber-400 flex-shrink-0">
|
<span className="inline-flex items-center gap-1 rounded-full border border-amber-500/30 bg-amber-500/10 px-2 py-0.5 font-label text-[0.625rem] uppercase tracking-wide text-amber-400 shrink-0">
|
||||||
<Wrench className="h-3 w-3" />
|
<Wrench className="h-3 w-3" />
|
||||||
Maintenance
|
Maintenance
|
||||||
</span>
|
</span>
|
||||||
@@ -62,11 +62,11 @@ export function TreeListView({
|
|||||||
)}
|
)}
|
||||||
{tree.is_public ? (
|
{tree.is_public ? (
|
||||||
<span title="Public tree">
|
<span title="Public tree">
|
||||||
<Globe className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
<Globe className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span title="Private tree">
|
<span title="Private tree">
|
||||||
<Lock className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
<Lock className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -90,7 +90,7 @@ export function TreeListView({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right: Metadata and Actions */}
|
{/* Right: Metadata and Actions */}
|
||||||
<div className="flex items-center gap-3 flex-shrink-0">
|
<div className="flex items-center gap-3 shrink-0">
|
||||||
<div className="hidden sm:flex flex-col items-end text-xs text-muted-foreground">
|
<div className="hidden sm:flex flex-col items-end text-xs text-muted-foreground">
|
||||||
<span>v{tree.version}</span>
|
<span>v{tree.version}</span>
|
||||||
<span>{tree.usage_count} uses</span>
|
<span>{tree.usage_count} uses</span>
|
||||||
|
|||||||
@@ -173,13 +173,13 @@ export function TreeTableView({
|
|||||||
{tree.name}
|
{tree.name}
|
||||||
</span>
|
</span>
|
||||||
{tree.status === 'draft' && (
|
{tree.status === 'draft' && (
|
||||||
<span className="inline-flex items-center gap-1 rounded-full bg-yellow-400/10 px-2 py-0.5 text-xs font-medium text-yellow-400 flex-shrink-0">
|
<span className="inline-flex items-center gap-1 rounded-full bg-yellow-400/10 px-2 py-0.5 text-xs font-medium text-yellow-400 shrink-0">
|
||||||
<FileText className="h-3 w-3" />
|
<FileText className="h-3 w-3" />
|
||||||
Draft
|
Draft
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{tree.tree_type === 'maintenance' && (
|
{tree.tree_type === 'maintenance' && (
|
||||||
<span className="inline-flex items-center gap-1 rounded-full border border-amber-500/30 bg-amber-500/10 px-2 py-0.5 font-label text-[0.625rem] uppercase tracking-wide text-amber-400 flex-shrink-0">
|
<span className="inline-flex items-center gap-1 rounded-full border border-amber-500/30 bg-amber-500/10 px-2 py-0.5 font-label text-[0.625rem] uppercase tracking-wide text-amber-400 shrink-0">
|
||||||
<Wrench className="h-3 w-3" />
|
<Wrench className="h-3 w-3" />
|
||||||
Maintenance
|
Maintenance
|
||||||
</span>
|
</span>
|
||||||
@@ -191,11 +191,11 @@ export function TreeTableView({
|
|||||||
)}
|
)}
|
||||||
{tree.is_public ? (
|
{tree.is_public ? (
|
||||||
<span title="Public tree">
|
<span title="Public tree">
|
||||||
<Globe className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
<Globe className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span title="Private tree">
|
<span title="Private tree">
|
||||||
<Lock className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
<Lock className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export function BatchLaunchModal({ treeId, treeName, onClose, onLaunched }: Batc
|
|||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-xs">
|
||||||
<div className="w-full max-w-lg rounded-xl border border-border bg-card shadow-2xl">
|
<div className="w-full max-w-lg rounded-xl border border-border bg-card shadow-2xl">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between border-b border-border px-6 py-4">
|
<div className="flex items-center justify-between border-b border-border px-6 py-4">
|
||||||
@@ -116,7 +116,7 @@ export function BatchLaunchModal({ treeId, treeName, onClose, onLaunched }: Batc
|
|||||||
Server names (one per line)
|
Server names (one per line)
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
className="h-40 w-full rounded-lg border border-border bg-card px-3 py-2 text-[0.875rem] text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
|
className="h-40 w-full rounded-lg border border-border bg-card px-3 py-2 text-[0.875rem] text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
placeholder={"RDS-01\nRDS-02\nRDS-03"}
|
placeholder={"RDS-01\nRDS-02\nRDS-03"}
|
||||||
value={manualInput}
|
value={manualInput}
|
||||||
onChange={e => setManualInput(e.target.value)}
|
onChange={e => setManualInput(e.target.value)}
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ export function BatchStatusCard({ session, targetLabel, treeId, batchId }: Batch
|
|||||||
{isNotStarted && (
|
{isNotStarted && (
|
||||||
<button
|
<button
|
||||||
onClick={handleStart}
|
onClick={handleStart}
|
||||||
className="flex items-center gap-1.5 rounded-lg bg-gradient-brand px-3 py-1.5 text-[0.8125rem] font-medium text-white shadow-sm shadow-primary/20 hover:opacity-90 transition-opacity"
|
className="flex items-center gap-1.5 rounded-lg bg-gradient-brand px-3 py-1.5 text-[0.8125rem] font-medium text-white shadow-xs shadow-primary/20 hover:opacity-90 transition-opacity"
|
||||||
>
|
>
|
||||||
<Play className="h-3.5 w-3.5" />
|
<Play className="h-3.5 w-3.5" />
|
||||||
Start
|
Start
|
||||||
|
|||||||
@@ -36,13 +36,13 @@ export function IntakeFieldEditor({ field, onUpdate, onRemove }: IntakeFieldEdit
|
|||||||
value={field.label}
|
value={field.label}
|
||||||
onChange={(e) => onUpdate({ label: e.target.value })}
|
onChange={(e) => onUpdate({ label: e.target.value })}
|
||||||
placeholder="Field label"
|
placeholder="Field label"
|
||||||
className="min-w-0 flex-1 rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
|
className="min-w-0 flex-1 rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<select
|
<select
|
||||||
value={field.field_type}
|
value={field.field_type}
|
||||||
onChange={(e) => onUpdate({ field_type: e.target.value as IntakeFieldType })}
|
onChange={(e) => onUpdate({ field_type: e.target.value as IntakeFieldType })}
|
||||||
className="rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
|
className="rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
{FIELD_TYPE_OPTIONS.map((opt) => (
|
{FIELD_TYPE_OPTIONS.map((opt) => (
|
||||||
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||||||
@@ -84,7 +84,7 @@ export function IntakeFieldEditor({ field, onUpdate, onRemove }: IntakeFieldEdit
|
|||||||
value={field.variable_name}
|
value={field.variable_name}
|
||||||
onChange={(e) => onUpdate({ variable_name: e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, '') })}
|
onChange={(e) => onUpdate({ variable_name: e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, '') })}
|
||||||
placeholder="e.g. server_name"
|
placeholder="e.g. server_name"
|
||||||
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
<p className="mt-0.5 text-[10px] text-muted-foreground">Used as [VAR:{field.variable_name}]</p>
|
<p className="mt-0.5 text-[10px] text-muted-foreground">Used as [VAR:{field.variable_name}]</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -96,7 +96,7 @@ export function IntakeFieldEditor({ field, onUpdate, onRemove }: IntakeFieldEdit
|
|||||||
value={field.placeholder || ''}
|
value={field.placeholder || ''}
|
||||||
onChange={(e) => onUpdate({ placeholder: e.target.value || undefined })}
|
onChange={(e) => onUpdate({ placeholder: e.target.value || undefined })}
|
||||||
placeholder="Hint text"
|
placeholder="Hint text"
|
||||||
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ export function IntakeFieldEditor({ field, onUpdate, onRemove }: IntakeFieldEdit
|
|||||||
value={field.help_text || ''}
|
value={field.help_text || ''}
|
||||||
onChange={(e) => onUpdate({ help_text: e.target.value || undefined })}
|
onChange={(e) => onUpdate({ help_text: e.target.value || undefined })}
|
||||||
placeholder="Description or instructions"
|
placeholder="Description or instructions"
|
||||||
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ export function IntakeFieldEditor({ field, onUpdate, onRemove }: IntakeFieldEdit
|
|||||||
value={field.default_value || ''}
|
value={field.default_value || ''}
|
||||||
onChange={(e) => onUpdate({ default_value: e.target.value || undefined })}
|
onChange={(e) => onUpdate({ default_value: e.target.value || undefined })}
|
||||||
placeholder="Pre-filled value"
|
placeholder="Pre-filled value"
|
||||||
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ export function IntakeFieldEditor({ field, onUpdate, onRemove }: IntakeFieldEdit
|
|||||||
value={field.group_name || ''}
|
value={field.group_name || ''}
|
||||||
onChange={(e) => onUpdate({ group_name: e.target.value || undefined })}
|
onChange={(e) => onUpdate({ group_name: e.target.value || undefined })}
|
||||||
placeholder="e.g. Network Settings"
|
placeholder="e.g. Network Settings"
|
||||||
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ export function IntakeFieldEditor({ field, onUpdate, onRemove }: IntakeFieldEdit
|
|||||||
}}
|
}}
|
||||||
placeholder="Option 1 Option 2 Option 3"
|
placeholder="Option 1 Option 2 Option 3"
|
||||||
rows={3}
|
rows={3}
|
||||||
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export function IntakeFormBuilder() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{intakeForm.length === 0 ? (
|
{intakeForm.length === 0 ? (
|
||||||
<div className="rounded-lg border border-dashed border-border bg-white/[0.02] py-8 text-center">
|
<div className="rounded-lg border border-dashed border-border bg-white/2 py-8 text-center">
|
||||||
<FileText className="mx-auto mb-2 h-8 w-8 text-muted-foreground" />
|
<FileText className="mx-auto mb-2 h-8 w-8 text-muted-foreground" />
|
||||||
<p className="text-sm text-muted-foreground">No intake form fields yet</p>
|
<p className="text-sm text-muted-foreground">No intake form fields yet</p>
|
||||||
<p className="mt-1 text-xs text-muted-foreground">
|
<p className="mt-1 text-xs text-muted-foreground">
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ export function MaintenanceScheduleSection({ treeId, onScheduleLoaded }: Mainten
|
|||||||
<select
|
<select
|
||||||
value={frequency}
|
value={frequency}
|
||||||
onChange={(e) => setFrequency(e.target.value)}
|
onChange={(e) => setFrequency(e.target.value)}
|
||||||
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
{FREQUENCY_OPTIONS.map(opt => (
|
{FREQUENCY_OPTIONS.map(opt => (
|
||||||
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||||||
@@ -172,7 +172,7 @@ export function MaintenanceScheduleSection({ treeId, onScheduleLoaded }: Mainten
|
|||||||
max={23}
|
max={23}
|
||||||
value={hour}
|
value={hour}
|
||||||
onChange={(e) => setHour(Number(e.target.value))}
|
onChange={(e) => setHour(Number(e.target.value))}
|
||||||
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -183,7 +183,7 @@ export function MaintenanceScheduleSection({ treeId, onScheduleLoaded }: Mainten
|
|||||||
max={59}
|
max={59}
|
||||||
value={minute}
|
value={minute}
|
||||||
onChange={(e) => setMinute(Number(e.target.value))}
|
onChange={(e) => setMinute(Number(e.target.value))}
|
||||||
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -194,7 +194,7 @@ export function MaintenanceScheduleSection({ treeId, onScheduleLoaded }: Mainten
|
|||||||
<select
|
<select
|
||||||
value={timezone}
|
value={timezone}
|
||||||
onChange={(e) => setTimezone(e.target.value)}
|
onChange={(e) => setTimezone(e.target.value)}
|
||||||
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
{TIMEZONE_OPTIONS.map(tz => (
|
{TIMEZONE_OPTIONS.map(tz => (
|
||||||
<option key={tz} value={tz}>{tz}</option>
|
<option key={tz} value={tz}>{tz}</option>
|
||||||
@@ -209,7 +209,7 @@ export function MaintenanceScheduleSection({ treeId, onScheduleLoaded }: Mainten
|
|||||||
<select
|
<select
|
||||||
value={selectedTargetListId}
|
value={selectedTargetListId}
|
||||||
onChange={(e) => setSelectedTargetListId(e.target.value)}
|
onChange={(e) => setSelectedTargetListId(e.target.value)}
|
||||||
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
<option value="">None — manual targets only</option>
|
<option value="">None — manual targets only</option>
|
||||||
{targetLists.map(tl => (
|
{targetLists.map(tl => (
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
value={step.title}
|
value={step.title}
|
||||||
onChange={(e) => onUpdate({ title: e.target.value })}
|
onChange={(e) => onUpdate({ title: e.target.value })}
|
||||||
placeholder="Section title"
|
placeholder="Section title"
|
||||||
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -74,7 +74,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
type="text"
|
type="text"
|
||||||
value={step.title}
|
value={step.title}
|
||||||
onChange={(e) => onUpdate({ title: e.target.value })}
|
onChange={(e) => onUpdate({ title: e.target.value })}
|
||||||
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
onChange={(e) => onUpdate({ estimated_minutes: e.target.value ? parseInt(e.target.value) : undefined })}
|
onChange={(e) => onUpdate({ estimated_minutes: e.target.value ? parseInt(e.target.value) : undefined })}
|
||||||
placeholder="—"
|
placeholder="—"
|
||||||
min={1}
|
min={1}
|
||||||
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
onChange={(e) => onUpdate({ description: e.target.value })}
|
onChange={(e) => onUpdate({ description: e.target.value })}
|
||||||
placeholder="Step instructions. Use [VAR:name] for variables."
|
placeholder="Step instructions. Use [VAR:name] for variables."
|
||||||
rows={4}
|
rows={4}
|
||||||
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
{availableVariables.length > 0 && (
|
{availableVariables.length > 0 && (
|
||||||
<div className="mt-1 flex flex-wrap gap-1">
|
<div className="mt-1 flex flex-wrap gap-1">
|
||||||
@@ -131,7 +131,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
onChange={(e) => onUpdate({ commands: e.target.value || undefined })}
|
onChange={(e) => onUpdate({ commands: e.target.value || undefined })}
|
||||||
placeholder="Install-WindowsFeature AD-Domain-Services -IncludeManagementTools"
|
placeholder="Install-WindowsFeature AD-Domain-Services -IncludeManagementTools"
|
||||||
rows={3}
|
rows={3}
|
||||||
className="w-full rounded border border-border bg-card px-3 py-2 font-mono text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-border bg-card px-3 py-2 font-mono text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -181,7 +181,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
onChange={(e) => onUpdate({ warning_text: e.target.value || undefined })}
|
onChange={(e) => onUpdate({ warning_text: e.target.value || undefined })}
|
||||||
placeholder="Caution: This will restart the service..."
|
placeholder="Caution: This will restart the service..."
|
||||||
rows={2}
|
rows={2}
|
||||||
className="w-full rounded border border-yellow-400/20 bg-yellow-400/5 px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-yellow-400/30 focus:outline-none focus:ring-1 focus:ring-yellow-400/20"
|
className="w-full rounded border border-yellow-400/20 bg-yellow-400/5 px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-yellow-400/30 focus:outline-hidden focus:ring-1 focus:ring-yellow-400/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -194,7 +194,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
value={step.expected_outcome || ''}
|
value={step.expected_outcome || ''}
|
||||||
onChange={(e) => onUpdate({ expected_outcome: e.target.value || undefined })}
|
onChange={(e) => onUpdate({ expected_outcome: e.target.value || undefined })}
|
||||||
placeholder="Server should respond with..."
|
placeholder="Server should respond with..."
|
||||||
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -210,7 +210,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
value={step.verification_prompt || ''}
|
value={step.verification_prompt || ''}
|
||||||
onChange={(e) => onUpdate({ verification_prompt: e.target.value || undefined })}
|
onChange={(e) => onUpdate({ verification_prompt: e.target.value || undefined })}
|
||||||
placeholder="Confirm the role was installed"
|
placeholder="Confirm the role was installed"
|
||||||
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -218,7 +218,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
<select
|
<select
|
||||||
value={step.verification_type || ''}
|
value={step.verification_type || ''}
|
||||||
onChange={(e) => onUpdate({ verification_type: e.target.value as 'checkbox' | 'text_input' || undefined })}
|
onChange={(e) => onUpdate({ verification_type: e.target.value as 'checkbox' | 'text_input' || undefined })}
|
||||||
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
<option value="">None</option>
|
<option value="">None</option>
|
||||||
<option value="checkbox">Checkbox (confirm done)</option>
|
<option value="checkbox">Checkbox (confirm done)</option>
|
||||||
@@ -239,7 +239,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
value={step.reference_url || ''}
|
value={step.reference_url || ''}
|
||||||
onChange={(e) => onUpdate({ reference_url: e.target.value || undefined })}
|
onChange={(e) => onUpdate({ reference_url: e.target.value || undefined })}
|
||||||
placeholder="https://learn.microsoft.com/..."
|
placeholder="https://learn.microsoft.com/..."
|
||||||
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-end pb-1">
|
<div className="flex items-end pb-1">
|
||||||
@@ -266,7 +266,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
onChange={(e) => onUpdate({
|
onChange={(e) => onUpdate({
|
||||||
library_visibility: e.target.value === '' ? undefined : e.target.value as 'team' | 'public'
|
library_visibility: e.target.value === '' ? undefined : e.target.value as 'team' | 'public'
|
||||||
})}
|
})}
|
||||||
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
<option value="">Inherit from flow</option>
|
<option value="">Inherit from flow</option>
|
||||||
<option value="team">Team only</option>
|
<option value="team">Team only</option>
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ export function StepList({ onStepContextMenu }: StepListProps) {
|
|||||||
type="text"
|
type="text"
|
||||||
value={step.title}
|
value={step.title}
|
||||||
onChange={(e) => updateStep(step.id, { title: e.target.value })}
|
onChange={(e) => updateStep(step.id, { title: e.target.value })}
|
||||||
className="flex-1 bg-transparent text-sm text-muted-foreground focus:outline-none"
|
className="flex-1 bg-transparent text-sm text-muted-foreground focus:outline-hidden"
|
||||||
placeholder="Procedure Complete"
|
placeholder="Procedure Complete"
|
||||||
/>
|
/>
|
||||||
<span className="text-[10px] text-muted-foreground">END</span>
|
<span className="text-[10px] text-muted-foreground">END</span>
|
||||||
@@ -239,7 +239,7 @@ export function StepList({ onStepContextMenu }: StepListProps) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'group flex flex-col rounded-xl border border-border px-3 py-2.5 transition-colors',
|
'group flex flex-col rounded-xl border border-border px-3 py-2.5 transition-colors',
|
||||||
'hover:border-primary/30 hover:bg-accent/50',
|
'hover:border-primary/30 hover:bg-accent/50',
|
||||||
isGhost && 'border-l-2 border-dashed !border-l-primary/40 opacity-60'
|
isGhost && 'border-l-2 border-dashed border-l-primary/40! opacity-60'
|
||||||
)}
|
)}
|
||||||
onContextMenu={(e) => onStepContextMenu?.(e, step.id)}
|
onContextMenu={(e) => onStepContextMenu?.(e, step.id)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export function IntakeFormModal({ isOpen, fields, treeName, onSubmit, onCancel }
|
|||||||
const error = errors[field.variable_name]
|
const error = errors[field.variable_name]
|
||||||
|
|
||||||
const baseInputClass = cn(
|
const baseInputClass = cn(
|
||||||
'w-full rounded-lg border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1',
|
'w-full rounded-lg border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-hidden focus:ring-1',
|
||||||
error
|
error
|
||||||
? 'border-red-400/50 focus:border-red-400 focus:ring-red-400/20'
|
? 'border-red-400/50 focus:border-red-400 focus:ring-red-400/20'
|
||||||
: 'border-border focus:border-primary focus:ring-primary/20'
|
: 'border-border focus:border-primary focus:ring-primary/20'
|
||||||
@@ -211,7 +211,7 @@ export function IntakeFormModal({ isOpen, fields, treeName, onSubmit, onCancel }
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-xs">
|
||||||
<div className="mx-4 w-full max-w-lg rounded-2xl border border-border bg-[#0a0a0a] shadow-xl">
|
<div className="mx-4 w-full max-w-lg rounded-2xl border border-border bg-[#0a0a0a] shadow-xl">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="border-b border-border px-6 py-4">
|
<div className="border-b border-border px-6 py-4">
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ export function StepDetail({
|
|||||||
|
|
||||||
{/* Expected outcome */}
|
{/* Expected outcome */}
|
||||||
{'expected_outcome' in step && step.expected_outcome && (
|
{'expected_outcome' in step && step.expected_outcome && (
|
||||||
<div className="rounded-lg border border-border bg-white/[0.02] p-3">
|
<div className="rounded-lg border border-border bg-white/2 p-3">
|
||||||
<h4 className="mb-1 text-xs font-medium text-muted-foreground">Expected Outcome</h4>
|
<h4 className="mb-1 text-xs font-medium text-muted-foreground">Expected Outcome</h4>
|
||||||
<p className="text-sm text-muted-foreground">{resolve(step.expected_outcome)}</p>
|
<p className="text-sm text-muted-foreground">{resolve(step.expected_outcome)}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -162,7 +162,7 @@ export function StepDetail({
|
|||||||
|
|
||||||
{/* Verification */}
|
{/* Verification */}
|
||||||
{verificationPrompt && (
|
{verificationPrompt && (
|
||||||
<div className="rounded-lg border border-border bg-white/[0.02] p-3">
|
<div className="rounded-lg border border-border bg-white/2 p-3">
|
||||||
<h4 className="mb-2 text-xs font-medium text-muted-foreground">Verification</h4>
|
<h4 className="mb-2 text-xs font-medium text-muted-foreground">Verification</h4>
|
||||||
{verificationType === 'checkbox' ? (
|
{verificationType === 'checkbox' ? (
|
||||||
<label className="flex items-center gap-2 text-sm text-muted-foreground">
|
<label className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
@@ -184,7 +184,7 @@ export function StepDetail({
|
|||||||
onChange={(e) => onVerificationChange(e.target.value)}
|
onChange={(e) => onVerificationChange(e.target.value)}
|
||||||
disabled={isCompleted}
|
disabled={isCompleted}
|
||||||
placeholder="Enter observed value..."
|
placeholder="Enter observed value..."
|
||||||
className="w-full rounded border border-border bg-card px-3 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20 disabled:opacity-50"
|
className="w-full rounded border border-border bg-card px-3 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20 disabled:opacity-50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -200,7 +200,7 @@ export function StepDetail({
|
|||||||
onChange={(e) => onNotesChange(e.target.value)}
|
onChange={(e) => onNotesChange(e.target.value)}
|
||||||
placeholder="Add notes for this step..."
|
placeholder="Add notes for this step..."
|
||||||
rows={2}
|
rows={2}
|
||||||
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export function CSATModal({ isOpen, onClose, sessionId }: CSATModalProps) {
|
|||||||
placeholder="Any comments? (optional)"
|
placeholder="Any comments? (optional)"
|
||||||
maxLength={500}
|
maxLength={500}
|
||||||
rows={3}
|
rows={3}
|
||||||
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20 resize-none"
|
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20 resize-none"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export function ContinuationModal({
|
|||||||
'hover:border-border hover:bg-accent'
|
'hover:border-border hover:bg-accent'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-accent">
|
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-accent">
|
||||||
{nodeTypeIcons[node.type]}
|
{nodeTypeIcons[node.type]}
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
@@ -71,7 +71,7 @@ export function ContinuationModal({
|
|||||||
{nodeTypeLabels[node.type]}
|
{nodeTypeLabels[node.type]}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<ArrowRight className="h-4 w-4 flex-shrink-0 text-muted-foreground" />
|
<ArrowRight className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -98,7 +98,7 @@ export function ContinuationModal({
|
|||||||
'hover:border-amber-500 hover:bg-amber-500/10'
|
'hover:border-amber-500 hover:bg-amber-500/10'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full bg-amber-500/10">
|
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-amber-500/10">
|
||||||
<GitBranch className="h-5 w-5 text-amber-500" />
|
<GitBranch className="h-5 w-5 text-amber-500" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
@@ -111,7 +111,7 @@ export function ContinuationModal({
|
|||||||
|
|
||||||
{/* Warning */}
|
{/* Warning */}
|
||||||
<div className="mt-3 flex items-start gap-2 rounded-md bg-yellow-400/10 p-3">
|
<div className="mt-3 flex items-start gap-2 rounded-md bg-yellow-400/10 p-3">
|
||||||
<AlertTriangle className="mt-0.5 h-4 w-4 flex-shrink-0 text-yellow-400" />
|
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-yellow-400" />
|
||||||
<p className="text-sm text-yellow-400">
|
<p className="text-sm text-yellow-400">
|
||||||
You'll need to complete this branch manually or mark the issue as resolved.
|
You'll need to complete this branch manually or mark the issue as resolved.
|
||||||
Custom branches can be saved as a personal tree when your session ends.
|
Custom branches can be saved as a personal tree when your session ends.
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ export function ExportPreviewModal({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'h-96 w-full resize-y rounded-md border border-border bg-card p-4',
|
'h-96 w-full resize-y rounded-md border border-border bg-card p-4',
|
||||||
'font-mono text-sm text-foreground',
|
'font-mono text-sm text-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -153,7 +153,7 @@ export function ExportPreviewModal({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium',
|
'flex items-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium',
|
||||||
'text-muted-foreground hover:bg-accent hover:text-foreground',
|
'text-muted-foreground hover:bg-accent hover:text-foreground',
|
||||||
'focus:outline-none focus:ring-2 focus:ring-primary/20'
|
'focus:outline-hidden focus:ring-2 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{copied ? (
|
{copied ? (
|
||||||
@@ -173,7 +173,7 @@ export function ExportPreviewModal({
|
|||||||
onClick={handleDownload}
|
onClick={handleDownload}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-2 rounded-md bg-gradient-brand text-white shadow-lg shadow-primary/20 px-3 py-2 text-sm font-medium',
|
'flex items-center gap-2 rounded-md bg-gradient-brand text-white shadow-lg shadow-primary/20 px-3 py-2 text-sm font-medium',
|
||||||
'hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-primary/20'
|
'hover:opacity-90 focus:outline-hidden focus:ring-2 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Download className="h-4 w-4" />
|
<Download className="h-4 w-4" />
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export function ForkTreeModal({
|
|||||||
<Modal isOpen={isOpen} onClose={onClose} title="Save Custom Tree?" footer={footer}>
|
<Modal isOpen={isOpen} onClose={onClose} title="Save Custom Tree?" footer={footer}>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-start gap-3 rounded-lg bg-accent/50 p-4">
|
<div className="flex items-start gap-3 rounded-lg bg-accent/50 p-4">
|
||||||
<div className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full bg-accent">
|
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-accent">
|
||||||
<GitFork className="h-5 w-5 text-foreground" />
|
<GitFork className="h-5 w-5 text-foreground" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -107,7 +107,7 @@ export function ForkTreeModal({
|
|||||||
placeholder="My Custom Tree"
|
placeholder="My Custom Tree"
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||||
'focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20'
|
'focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -124,7 +124,7 @@ export function ForkTreeModal({
|
|||||||
rows={3}
|
rows={3}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||||
'focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20',
|
'focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20',
|
||||||
'resize-none'
|
'resize-none'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export function SaveSessionAsTreeModal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs">
|
||||||
<div className="bg-card border border-border w-full max-w-lg rounded-2xl p-6 shadow-lg">
|
<div className="bg-card border border-border w-full max-w-lg rounded-2xl p-6 shadow-lg">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<div className="mb-4 flex items-center justify-between">
|
||||||
@@ -70,7 +70,7 @@ export function SaveSessionAsTreeModal({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||||
'placeholder:text-muted-foreground',
|
'placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'disabled:opacity-50'
|
'disabled:opacity-50'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -91,7 +91,7 @@ export function SaveSessionAsTreeModal({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||||
'placeholder:text-muted-foreground',
|
'placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'disabled:opacity-50'
|
'disabled:opacity-50'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ export function ScratchpadSidebar({ sessionId, initialContent, onSave, onOpenCha
|
|||||||
{/* Mobile backdrop */}
|
{/* Mobile backdrop */}
|
||||||
{!isCollapsed && (
|
{!isCollapsed && (
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 z-30 bg-black/80 backdrop-blur-sm sm:hidden"
|
className="fixed inset-0 z-30 bg-black/80 backdrop-blur-xs sm:hidden"
|
||||||
onClick={() => setIsCollapsed(true)}
|
onClick={() => setIsCollapsed(true)}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
@@ -203,7 +203,7 @@ export function ScratchpadSidebar({ sessionId, initialContent, onSave, onOpenCha
|
|||||||
className={cn(
|
className={cn(
|
||||||
'h-full min-h-[200px] w-full resize-none rounded-md border-0 bg-transparent p-0 text-sm',
|
'h-full min-h-[200px] w-full resize-none rounded-md border-0 bg-transparent p-0 text-sm',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:outline-none focus:ring-0'
|
'focus:outline-hidden focus:ring-0'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
|
|||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-md border border-border bg-card py-2 pl-9 pr-3',
|
'w-full rounded-md border border-border bg-card py-2 pl-9 pr-3',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -118,7 +118,7 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
|
|||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-md border border-border bg-card py-2 pl-9 pr-3',
|
'w-full rounded-md border border-border bg-card py-2 pl-9 pr-3',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -129,7 +129,7 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
|
|||||||
onChange={(e) => handleFilterChange('treeName', e.target.value)}
|
onChange={(e) => handleFilterChange('treeName', e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border bg-card px-3 py-2',
|
'rounded-md border border-border bg-card px-3 py-2',
|
||||||
'text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
'text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'sm:min-w-[200px]'
|
'sm:min-w-[200px]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ export function SessionOutcomeModal({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border border-border bg-card px-3 py-2',
|
'mt-1 block w-full rounded-md border border-border bg-card px-3 py-2',
|
||||||
'text-sm text-foreground placeholder:text-muted-foreground',
|
'text-sm text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -126,7 +126,7 @@ export function SessionOutcomeModal({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border border-border bg-card px-3 py-2',
|
'mt-1 block w-full rounded-md border border-border bg-card px-3 py-2',
|
||||||
'text-sm text-foreground placeholder:text-muted-foreground',
|
'text-sm text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ export function SessionTimeline({
|
|||||||
{decisions.map((decision, index) => (
|
{decisions.map((decision, index) => (
|
||||||
<div key={index} className="ml-1 border-l-2 border-border pl-6">
|
<div key={index} className="ml-1 border-l-2 border-border pl-6">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<span className="absolute -left-[1.625rem] top-1 h-2 w-2 rounded-full bg-muted-foreground" />
|
<span className="absolute -left-6.5 top-1 h-2 w-2 rounded-full bg-muted-foreground" />
|
||||||
<div className="bg-card border border-border rounded-xl p-4">
|
<div className="bg-card border border-border rounded-xl p-4">
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
|
|||||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||||
{/* Backdrop */}
|
{/* Backdrop */}
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 bg-black/80 backdrop-blur-sm"
|
className="absolute inset-0 bg-black/80 backdrop-blur-xs"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -265,7 +265,7 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
|
|||||||
placeholder="e.g. Training link, Customer escalation"
|
placeholder="e.g. Training link, Customer escalation"
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground placeholder-muted-foreground',
|
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground placeholder-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
maxLength={100}
|
maxLength={100}
|
||||||
/>
|
/>
|
||||||
@@ -299,8 +299,8 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
|
|||||||
onChange={(e) => setCustomDatetime(e.target.value)}
|
onChange={(e) => setCustomDatetime(e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-2 w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
'mt-2 w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'[color-scheme:dark]'
|
'scheme-dark'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export function SharedSessionTreePreview({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-card border border-border rounded-2xl">
|
<div className="bg-card border border-border rounded-2xl">
|
||||||
<div className="sticky top-0 z-10 rounded-t-2xl border-b border-border bg-black/80 px-6 py-4 backdrop-blur">
|
<div className="sticky top-0 z-10 rounded-t-2xl border-b border-border bg-black/80 px-6 py-4 backdrop-blur-sm">
|
||||||
<h3 className="text-sm font-semibold text-foreground">Tree Structure</h3>
|
<h3 className="text-sm font-semibold text-foreground">Tree Structure</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="max-h-[600px] overflow-y-auto py-2">
|
<div className="max-h-[600px] overflow-y-auto py-2">
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export function StepRatingModal({
|
|||||||
const getRating = (stepId: string) => ratings.get(stepId)
|
const getRating = (stepId: string) => ratings.get(stepId)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm p-4">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs p-4">
|
||||||
<div className="bg-card border border-border w-full max-w-2xl max-h-[90vh] flex flex-col rounded-2xl shadow-lg">
|
<div className="bg-card border border-border w-full max-w-2xl max-h-[90vh] flex flex-col rounded-2xl shadow-lg">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between border-b border-border px-6 py-4">
|
<div className="flex items-center justify-between border-b border-border px-6 py-4">
|
||||||
@@ -174,7 +174,7 @@ export function StepRatingModal({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||||
'placeholder:text-muted-foreground',
|
'placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'disabled:opacity-50'
|
'disabled:opacity-50'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export function VariablePromptModal({ prompt, onSubmit, onCancel }: VariableProm
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs">
|
||||||
<div className="bg-card border border-border w-full max-w-md rounded-2xl p-6 shadow-lg">
|
<div className="bg-card border border-border w-full max-w-md rounded-2xl p-6 shadow-lg">
|
||||||
<h2 className="mb-1 text-lg font-semibold text-foreground">Input Required</h2>
|
<h2 className="mb-1 text-lg font-semibold text-foreground">Input Required</h2>
|
||||||
<p className="mb-4 text-sm text-muted-foreground">
|
<p className="mb-4 text-sm text-muted-foreground">
|
||||||
@@ -40,7 +40,7 @@ export function VariablePromptModal({ prompt, onSubmit, onCancel }: VariableProm
|
|||||||
autoFocus
|
autoFocus
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground',
|
'w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||||
'placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
|
'placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export function PinnedFlowsSection({ flows, onUnpin }: PinnedFlowsSectionProps)
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="overflow-hidden transition-[max-height] duration-[250ms] ease-out"
|
className="overflow-hidden transition-[max-height] duration-250 ease-out"
|
||||||
style={{
|
style={{
|
||||||
maxHeight: collapsed ? 0 : showAll
|
maxHeight: collapsed ? 0 : showAll
|
||||||
? `${flows.length * 36 + 40}px`
|
? `${flows.length * 36 + 40}px`
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export function CustomStepModal({ isOpen, onClose, onInsertStep }: CustomStepMod
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-end justify-center bg-black/80 backdrop-blur-sm sm:items-center sm:p-4">
|
<div className="fixed inset-0 z-50 flex items-end justify-center bg-black/80 backdrop-blur-xs sm:items-center sm:p-4">
|
||||||
<div className="relative flex h-[95vh] w-full max-w-full flex-col border border-border bg-card shadow-lg sm:h-[90vh] sm:max-w-4xl sm:rounded-2xl">
|
<div className="relative flex h-[95vh] w-full max-w-full flex-col border border-border bg-card shadow-lg sm:h-[90vh] sm:max-w-4xl sm:rounded-2xl">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between border-b border-border p-4">
|
<div className="flex items-center justify-between border-b border-border p-4">
|
||||||
@@ -133,7 +133,7 @@ export function CustomStepModal({ isOpen, onClose, onInsertStep }: CustomStepMod
|
|||||||
|
|
||||||
{/* Loading Overlay */}
|
{/* Loading Overlay */}
|
||||||
{isSubmitting && (
|
{isSubmitting && (
|
||||||
<div className="absolute inset-0 flex items-center justify-center bg-black/80 backdrop-blur-sm">
|
<div className="absolute inset-0 flex items-center justify-center bg-black/80 backdrop-blur-xs">
|
||||||
<div className="flex flex-col items-center gap-3">
|
<div className="flex flex-col items-center gap-3">
|
||||||
<Spinner className="border-t-foreground" />
|
<Spinner className="border-t-foreground" />
|
||||||
<p className="text-sm text-muted-foreground">Creating step...</p>
|
<p className="text-sm text-muted-foreground">Creating step...</p>
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
|
|||||||
const visibleReviews = showAllReviews ? allTextReviews : allTextReviews.slice(0, 3)
|
const visibleReviews = showAllReviews ? allTextReviews : allTextReviews.slice(0, 3)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs">
|
||||||
<div className="relative flex h-[90vh] w-full max-w-3xl flex-col bg-card border border-border rounded-2xl shadow-lg">
|
<div className="relative flex h-[90vh] w-full max-w-3xl flex-col bg-card border border-border rounded-2xl shadow-lg">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-start justify-between border-b border-border p-6 pb-4">
|
<div className="flex items-start justify-between border-b border-border p-6 pb-4">
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
|
|||||||
onChange={(e) => setTitle(e.target.value)}
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
placeholder="Enter step title"
|
placeholder="Enter step title"
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-md border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20',
|
'w-full rounded-md border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20',
|
||||||
errors.title ? 'border-red-400/50' : 'border-border'
|
errors.title ? 'border-red-400/50' : 'border-border'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -201,7 +201,7 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
|
|||||||
placeholder="Describe what to do in this step..."
|
placeholder="Describe what to do in this step..."
|
||||||
rows={6}
|
rows={6}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-md border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20',
|
'w-full rounded-md border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20',
|
||||||
errors.instructions ? 'border-red-400/50' : 'border-border'
|
errors.instructions ? 'border-red-400/50' : 'border-border'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -221,7 +221,7 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
|
|||||||
onChange={(e) => setHelpText(e.target.value)}
|
onChange={(e) => setHelpText(e.target.value)}
|
||||||
placeholder="Additional context or tips..."
|
placeholder="Additional context or tips..."
|
||||||
rows={3}
|
rows={3}
|
||||||
className="w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -296,7 +296,7 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
|
|||||||
id="category"
|
id="category"
|
||||||
value={categoryId}
|
value={categoryId}
|
||||||
onChange={(e) => setCategoryId(e.target.value)}
|
onChange={(e) => setCategoryId(e.target.value)}
|
||||||
className="w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
<option value="">None</option>
|
<option value="">None</option>
|
||||||
{categories.map(cat => (
|
{categories.map(cat => (
|
||||||
@@ -318,7 +318,7 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
|
|||||||
onChange={(e) => setTagInput(e.target.value)}
|
onChange={(e) => setTagInput(e.target.value)}
|
||||||
onKeyDown={handleTagInputKeyDown}
|
onKeyDown={handleTagInputKeyDown}
|
||||||
placeholder="Type tag and press Enter"
|
placeholder="Type tag and press Enter"
|
||||||
className="flex-1 rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
|
className="flex-1 rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -359,7 +359,7 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
|
|||||||
id="visibility"
|
id="visibility"
|
||||||
value={visibility}
|
value={visibility}
|
||||||
onChange={(e) => setVisibility(e.target.value as 'private' | 'team' | 'public')}
|
onChange={(e) => setVisibility(e.target.value as 'private' | 'team' | 'public')}
|
||||||
className="w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
<option value="private">Private (only me)</option>
|
<option value="private">Private (only me)</option>
|
||||||
<option value="team">Team (my team members)</option>
|
<option value="team">Team (my team members)</option>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export function StepFormModal({ isOpen, onClose, onSuccess, editingStep }: StepF
|
|||||||
} : undefined
|
} : undefined
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm p-4">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs p-4">
|
||||||
<div className="relative flex h-[90vh] w-full max-w-2xl flex-col bg-card border border-border rounded-2xl shadow-lg">
|
<div className="relative flex h-[90vh] w-full max-w-2xl flex-col bg-card border border-border rounded-2xl shadow-lg">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between border-b border-border p-6 pb-4">
|
<div className="flex items-center justify-between border-b border-border p-6 pb-4">
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
|
|||||||
placeholder="Search steps..."
|
placeholder="Search steps..."
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
className="w-full rounded-md border border-border bg-card py-2 pl-10 pr-4 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-md border border-border bg-card py-2 pl-10 pr-4 text-sm text-foreground placeholder:text-muted-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -162,7 +162,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
|
|||||||
aria-label="Filter by category"
|
aria-label="Filter by category"
|
||||||
value={selectedCategoryId || ''}
|
value={selectedCategoryId || ''}
|
||||||
onChange={(e) => setSelectedCategoryId(e.target.value || undefined)}
|
onChange={(e) => setSelectedCategoryId(e.target.value || undefined)}
|
||||||
className="rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
|
className="rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
<option value="">All Categories</option>
|
<option value="">All Categories</option>
|
||||||
{categories.map(cat => (
|
{categories.map(cat => (
|
||||||
@@ -175,7 +175,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
|
|||||||
aria-label="Filter by step type"
|
aria-label="Filter by step type"
|
||||||
value={selectedStepType || ''}
|
value={selectedStepType || ''}
|
||||||
onChange={(e) => setSelectedStepType((e.target.value as 'decision' | 'action' | 'solution') || undefined)}
|
onChange={(e) => setSelectedStepType((e.target.value as 'decision' | 'action' | 'solution') || undefined)}
|
||||||
className="rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
|
className="rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
<option value="">All Types</option>
|
<option value="">All Types</option>
|
||||||
<option value="decision">Decision</option>
|
<option value="decision">Decision</option>
|
||||||
@@ -188,7 +188,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
|
|||||||
aria-label="Filter by minimum rating"
|
aria-label="Filter by minimum rating"
|
||||||
value={minRating?.toString() || ''}
|
value={minRating?.toString() || ''}
|
||||||
onChange={(e) => setMinRating(e.target.value ? Number(e.target.value) : undefined)}
|
onChange={(e) => setMinRating(e.target.value ? Number(e.target.value) : undefined)}
|
||||||
className="rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
|
className="rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
<option value="">Any Rating</option>
|
<option value="">Any Rating</option>
|
||||||
<option value="4">4+ Stars</option>
|
<option value="4">4+ Stars</option>
|
||||||
@@ -201,7 +201,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
|
|||||||
aria-label="Sort steps by"
|
aria-label="Sort steps by"
|
||||||
value={sortBy}
|
value={sortBy}
|
||||||
onChange={(e) => setSortBy(e.target.value as 'recent' | 'popular' | 'highest_rated' | 'most_used')}
|
onChange={(e) => setSortBy(e.target.value as 'recent' | 'popular' | 'highest_rated' | 'most_used')}
|
||||||
className="rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
|
className="rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
<option value="recent">Most Recent</option>
|
<option value="recent">Most Recent</option>
|
||||||
<option value="popular">Most Popular</option>
|
<option value="popular">Most Popular</option>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export function AIFixReviewModal({ fixes, onApply, onApplyAll, onClose }: AIFixR
|
|||||||
const allHandled = pendingFixes.length === 0
|
const allHandled = pendingFixes.length === 0
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm p-4">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs p-4">
|
||||||
<div className="relative flex h-[80vh] w-full max-w-2xl flex-col bg-card border border-border rounded-2xl shadow-lg">
|
<div className="relative flex h-[80vh] w-full max-w-2xl flex-col bg-card border border-border rounded-2xl shadow-lg">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between border-b border-border px-6 py-4">
|
<div className="flex items-center justify-between border-b border-border px-6 py-4">
|
||||||
@@ -127,7 +127,7 @@ export function AIFixReviewModal({ fixes, onApply, onApplyAll, onClose }: AIFixR
|
|||||||
<div className="mt-3 flex gap-2">
|
<div className="mt-3 flex gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => handleApply(fix)}
|
onClick={() => handleApply(fix)}
|
||||||
className="flex items-center gap-1 rounded-md bg-gradient-brand px-3 py-1.5 text-xs font-medium text-white shadow-sm shadow-primary/20 hover:opacity-90"
|
className="flex items-center gap-1 rounded-md bg-gradient-brand px-3 py-1.5 text-xs font-medium text-white shadow-xs shadow-primary/20 hover:opacity-90"
|
||||||
>
|
>
|
||||||
<Check className="h-3 w-3" />
|
<Check className="h-3 w-3" />
|
||||||
Apply
|
Apply
|
||||||
|
|||||||
@@ -140,13 +140,13 @@ function FlowCanvasInner({ selectedNodeId, onNodeSelect, onSelectAnswerType, onN
|
|||||||
className="dark bg-accent/30"
|
className="dark bg-accent/30"
|
||||||
>
|
>
|
||||||
<Background variant={BackgroundVariant.Dots} gap={20} size={1.5} color="hsl(var(--muted-foreground) / 0.25)" />
|
<Background variant={BackgroundVariant.Dots} gap={20} size={1.5} color="hsl(var(--muted-foreground) / 0.25)" />
|
||||||
<Controls showInteractive={false} className="!bg-card !border-border !shadow-lg" />
|
<Controls showInteractive={false} className="bg-card! border-border! shadow-lg!" />
|
||||||
{minimapVisible && (
|
{minimapVisible && (
|
||||||
<MiniMap
|
<MiniMap
|
||||||
pannable
|
pannable
|
||||||
zoomable
|
zoomable
|
||||||
nodeColor={minimapNodeColor}
|
nodeColor={minimapNodeColor}
|
||||||
className="!bg-card !border-border"
|
className="bg-card! border-border!"
|
||||||
nodeStrokeWidth={2}
|
nodeStrokeWidth={2}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ function FlowCanvasAnswerNodeComponent({ data, selected }: NodeProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Handle type="target" position={Position.Top} className="!bg-border !w-2 !h-2 !border-0" />
|
<Handle type="target" position={Position.Top} className="bg-border! w-2! h-2! border-0!" />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -61,7 +61,7 @@ function FlowCanvasAnswerNodeComponent({ data, selected }: NodeProps) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Handle type="source" position={Position.Bottom} className="!bg-border !w-2 !h-2 !border-0" />
|
<Handle type="source" position={Position.Bottom} className="bg-border! w-2! h-2! border-0!" />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,15 +62,15 @@ function FlowCanvasNodeComponent({ data, selected }: NodeProps) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Target handle at top */}
|
{/* Target handle at top */}
|
||||||
<Handle type="target" position={Position.Top} className="!bg-border !w-2 !h-2 !border-0" />
|
<Handle type="target" position={Position.Top} className="bg-border! w-2! h-2! border-0!" />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
onContextMenu={(e) => onContextMenu?.(e, node.id)}
|
onContextMenu={(e) => onContextMenu?.(e, node.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-[280px] rounded-xl border border-border bg-card shadow-sm cursor-pointer transition-all',
|
'w-[280px] rounded-xl border border-border bg-card shadow-xs cursor-pointer transition-all',
|
||||||
config.borderClass,
|
config.borderClass,
|
||||||
selected && 'ring-1 ring-primary shadow-md',
|
selected && 'ring-1 ring-primary shadow-md',
|
||||||
isGhost && 'border-dashed !border-primary/40 opacity-60'
|
isGhost && 'border-dashed border-primary/40! opacity-60'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@@ -175,7 +175,7 @@ function FlowCanvasNodeComponent({ data, selected }: NodeProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Source handle at bottom */}
|
{/* Source handle at bottom */}
|
||||||
<Handle type="source" position={Position.Bottom} className="!bg-border !w-2 !h-2 !border-0" />
|
<Handle type="source" position={Position.Bottom} className="bg-border! w-2! h-2! border-0!" />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export function MetadataSidePanel({ isOpen, onClose }: MetadataSidePanelProps) {
|
|||||||
<>
|
<>
|
||||||
{/* Backdrop — click to close */}
|
{/* Backdrop — click to close */}
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 z-40 bg-background/40 backdrop-blur-sm"
|
className="fixed inset-0 z-40 bg-background/40 backdrop-blur-xs"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
||||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
'bg-card text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
titleError ? 'border-red-400' : 'border-border'
|
titleError ? 'border-red-400' : 'border-border'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -107,7 +107,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
||||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
'bg-background text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -134,7 +134,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'block w-full rounded-md border border-border px-3 py-2 font-mono text-sm',
|
'block w-full rounded-md border border-border px-3 py-2 font-mono text-sm',
|
||||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
'bg-background text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -154,7 +154,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
||||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
'bg-background text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -195,7 +195,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
||||||
'bg-card text-foreground',
|
'bg-card text-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<option value="">Link to existing node...</option>
|
<option value="">Link to existing node...</option>
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
||||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
'bg-card text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
questionError ? 'border-red-400' : 'border-border'
|
questionError ? 'border-red-400' : 'border-border'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -126,7 +126,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
||||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
'bg-background text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -187,7 +187,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'block w-full rounded-md border px-3 py-2 text-sm',
|
'block w-full rounded-md border px-3 py-2 text-sm',
|
||||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
'bg-background text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary',
|
||||||
optionLabelError ? 'border-red-400' : 'border-border'
|
optionLabelError ? 'border-red-400' : 'border-border'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export function NodeFormResolution({ node, onUpdate }: NodeFormResolutionProps)
|
|||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
||||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
'bg-card text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
titleError ? 'border-red-400' : 'border-border'
|
titleError ? 'border-red-400' : 'border-border'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -103,7 +103,7 @@ Document what was done and the outcome.
|
|||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
||||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
'bg-background text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -134,7 +134,7 @@ Document what was done and the outcome.
|
|||||||
className={cn(
|
className={cn(
|
||||||
'block w-full rounded-md border border-border px-3 py-2 text-sm',
|
'block w-full rounded-md border border-border px-3 py-2 text-sm',
|
||||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
'bg-background text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ function NodeListItem({
|
|||||||
'group flex items-center gap-1 rounded-md px-2 py-1.5 text-sm transition-colors cursor-pointer',
|
'group flex items-center gap-1 rounded-md px-2 py-1.5 text-sm transition-colors cursor-pointer',
|
||||||
isRootNode
|
isRootNode
|
||||||
? isSelected
|
? isSelected
|
||||||
? 'bg-blue-500/20 ring-2 ring-blue-500 shadow-sm'
|
? 'bg-blue-500/20 ring-2 ring-blue-500 shadow-xs'
|
||||||
: 'bg-blue-500/10 border border-blue-500/30 hover:bg-blue-500/15'
|
: 'bg-blue-500/10 border border-blue-500/30 hover:bg-blue-500/15'
|
||||||
: isSelected
|
: isSelected
|
||||||
? 'bg-primary/10 ring-1 ring-primary'
|
? 'bg-primary/10 ring-1 ring-primary'
|
||||||
@@ -581,7 +581,7 @@ export function NodeList() {
|
|||||||
|
|
||||||
{/* Add Node Type Selector */}
|
{/* Add Node Type Selector */}
|
||||||
{addingToParent && (
|
{addingToParent && (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-xs">
|
||||||
<div className="w-full max-w-xs rounded-lg border border-border bg-card p-4 shadow-lg">
|
<div className="w-full max-w-xs rounded-lg border border-border bg-card p-4 shadow-lg">
|
||||||
<h3 className="mb-3 text-sm font-semibold">Select Node Type</h3>
|
<h3 className="mb-3 text-sm font-semibold">Select Node Type</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ export function NodePicker({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex-1 rounded-md border border-border px-2 py-1 text-sm',
|
'flex-1 rounded-md border border-border px-2 py-1 text-sm',
|
||||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
'bg-card text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -201,7 +201,7 @@ export function NodePicker({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'block w-full rounded-md border px-3 py-2 text-sm',
|
'block w-full rounded-md border px-3 py-2 text-sm',
|
||||||
'bg-card text-foreground',
|
'bg-card text-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
error ? 'border-red-400' : 'border-border'
|
error ? 'border-red-400' : 'border-border'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ interface AddNodePickerProps {
|
|||||||
|
|
||||||
function AddNodePicker({ onSelect, onCancel }: AddNodePickerProps) {
|
function AddNodePicker({ onSelect, onCancel }: AddNodePickerProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2 rounded-xl border border-dashed border-primary/40 bg-card px-3 py-2 shadow-sm">
|
<div className="flex items-center gap-2 rounded-xl border border-dashed border-primary/40 bg-card px-3 py-2 shadow-xs">
|
||||||
<span className="text-xs text-muted-foreground shrink-0">Add:</span>
|
<span className="text-xs text-muted-foreground shrink-0">Add:</span>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ export function TreeCanvasNode({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative rounded-xl border border-border bg-card shadow-sm transition-all duration-150',
|
'relative rounded-xl border border-border bg-card shadow-xs transition-all duration-150',
|
||||||
config.borderClass,
|
config.borderClass,
|
||||||
isExpanded && 'ring-1 ring-primary shadow-md',
|
isExpanded && 'ring-1 ring-primary shadow-md',
|
||||||
isSelected && !isExpanded && 'ring-1 ring-primary/50',
|
isSelected && !isExpanded && 'ring-1 ring-primary/50',
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export function TreeMetadataForm() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
||||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
'bg-card text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
nameError ? 'border-red-400' : 'border-border'
|
nameError ? 'border-red-400' : 'border-border'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -94,7 +94,7 @@ export function TreeMetadataForm() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
||||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
'bg-card text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -112,7 +112,7 @@ export function TreeMetadataForm() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
||||||
'bg-card text-foreground',
|
'bg-card text-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<option value="">No category</option>
|
<option value="">No category</option>
|
||||||
@@ -134,7 +134,7 @@ export function TreeMetadataForm() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'block min-w-0 flex-1 rounded-md border border-border px-3 py-2 text-sm',
|
'block min-w-0 flex-1 rounded-md border border-border px-3 py-2 text-sm',
|
||||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
'bg-card text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export function ValidationSummary({ errors, onSelectNode, onFixWithAI, isFixing
|
|||||||
'flex items-center gap-1.5 rounded-md px-3 py-1 text-xs font-medium transition-colors',
|
'flex items-center gap-1.5 rounded-md px-3 py-1 text-xs font-medium transition-colors',
|
||||||
isFixing
|
isFixing
|
||||||
? 'bg-primary/10 text-primary cursor-wait'
|
? 'bg-primary/10 text-primary cursor-wait'
|
||||||
: 'bg-gradient-brand text-white shadow-sm shadow-primary/20 hover:opacity-90'
|
: 'bg-gradient-brand text-white shadow-xs shadow-primary/20 hover:opacity-90'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isFixing ? (
|
{isFixing ? (
|
||||||
@@ -109,7 +109,7 @@ export function ValidationSummary({ errors, onSelectNode, onFixWithAI, isFixing
|
|||||||
: 'cursor-default'
|
: 'cursor-default'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<AlertCircle className="mt-0.5 h-4 w-4 flex-shrink-0 text-red-400" />
|
<AlertCircle className="mt-0.5 h-4 w-4 shrink-0 text-red-400" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<p className="text-red-400">{error.message}</p>
|
<p className="text-red-400">{error.message}</p>
|
||||||
{error.nodeId && (
|
{error.nodeId && (
|
||||||
@@ -133,7 +133,7 @@ export function ValidationSummary({ errors, onSelectNode, onFixWithAI, isFixing
|
|||||||
: 'cursor-default'
|
: 'cursor-default'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<AlertTriangle className="mt-0.5 h-4 w-4 flex-shrink-0 text-yellow-400" />
|
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-yellow-400" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<p className="text-yellow-400">{warning.message}</p>
|
<p className="text-yellow-400">{warning.message}</p>
|
||||||
{warning.nodeId && (
|
{warning.nodeId && (
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ export function TreePreviewNode({
|
|||||||
{/* Solution path indicator - shows when this branch leads to a solution */}
|
{/* Solution path indicator - shows when this branch leads to a solution */}
|
||||||
{leadsTosolution && (
|
{leadsTosolution && (
|
||||||
<div
|
<div
|
||||||
className="absolute -top-1.5 -right-1.5 flex items-center justify-center rounded-full bg-green-500/90 p-0.5 shadow-sm"
|
className="absolute -top-1.5 -right-1.5 flex items-center justify-center rounded-full bg-green-500/90 p-0.5 shadow-xs"
|
||||||
title="This branch leads to a solution"
|
title="This branch leads to a solution"
|
||||||
>
|
>
|
||||||
<CheckCircle className="h-3 w-3 text-foreground" />
|
<CheckCircle className="h-3 w-3 text-foreground" />
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ import { cn } from '@/lib/utils'
|
|||||||
import { Spinner } from '@/components/common/Spinner'
|
import { Spinner } from '@/components/common/Spinner'
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
'inline-flex items-center justify-center gap-2 font-medium transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 active:scale-[0.97]',
|
'inline-flex items-center justify-center gap-2 font-medium transition-all duration-200 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-primary/30 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 active:scale-[0.97]',
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
primary:
|
primary:
|
||||||
'bg-gradient-brand text-[#101114] font-semibold shadow-lg shadow-primary/20 hover:opacity-90',
|
'bg-gradient-brand text-brand-dark font-semibold shadow-lg shadow-primary/20 hover:opacity-90',
|
||||||
secondary:
|
secondary:
|
||||||
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground hover:border-[rgba(255,255,255,0.12)] hover:bg-[rgba(255,255,255,0.06)]',
|
'bg-[rgba(255,255,255,0.04)] border border-brand-border text-foreground hover:border-[rgba(255,255,255,0.12)] hover:bg-brand-border',
|
||||||
destructive:
|
destructive:
|
||||||
'bg-red-400/10 text-red-400 border border-red-400/20 hover:bg-red-400/20',
|
'bg-red-400/10 text-red-400 border border-red-400/20 hover:bg-red-400/20',
|
||||||
ghost:
|
ghost:
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export function Input({ className, error, id, ...props }: InputProps) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex h-9 w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
'flex h-9 w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||||
'placeholder:text-muted-foreground',
|
'placeholder:text-muted-foreground',
|
||||||
'focus:border-[rgba(6,182,212,0.3)] focus:outline-none focus:ring-1 focus:ring-primary/20',
|
'focus:border-[rgba(6,182,212,0.3)] focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
error && 'border-red-400/50 focus:border-red-400 focus:ring-red-400/20',
|
error && 'border-red-400/50 focus:border-red-400 focus:ring-red-400/20',
|
||||||
className
|
className
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export function Skeleton({ className, ...props }: SkeletonProps) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'animate-pulse rounded-lg bg-[rgba(255,255,255,0.06)]',
|
'animate-pulse rounded-lg bg-brand-border',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export function Textarea({ className, error, id, ...props }: TextareaProps) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
'flex w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||||
'placeholder:text-muted-foreground',
|
'placeholder:text-muted-foreground',
|
||||||
'focus:border-[rgba(6,182,212,0.3)] focus:outline-none focus:ring-1 focus:ring-primary/20',
|
'focus:border-[rgba(6,182,212,0.3)] focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
error && 'border-red-400/50 focus:border-red-400 focus:ring-red-400/20',
|
error && 'border-red-400/50 focus:border-red-400 focus:ring-red-400/20',
|
||||||
className
|
className
|
||||||
|
|||||||
@@ -1,6 +1,289 @@
|
|||||||
@tailwind base;
|
@import 'tailwindcss';
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
--color-brand-gradient-from: #06b6d4;
|
||||||
|
--color-brand-gradient-to: #22d3ee;
|
||||||
|
|
||||||
|
--color-brand-dark: #101114;
|
||||||
|
--color-brand-dark-card: #14161a;
|
||||||
|
--color-brand-dark-surface: #14161a;
|
||||||
|
|
||||||
|
--color-brand-text-primary: #f8fafc;
|
||||||
|
--color-brand-text-secondary: #8891a0;
|
||||||
|
--color-brand-text-muted: #5a6170;
|
||||||
|
|
||||||
|
--color-brand-border: rgba(255, 255, 255, 0.06);
|
||||||
|
|
||||||
|
--color-border: hsl(var(--border));
|
||||||
|
--color-input: hsl(var(--input));
|
||||||
|
--color-ring: hsl(var(--ring));
|
||||||
|
--color-background: hsl(var(--background));
|
||||||
|
--color-foreground: hsl(var(--foreground));
|
||||||
|
|
||||||
|
--color-primary: hsl(var(--primary));
|
||||||
|
--color-primary-foreground: hsl(var(--primary-foreground));
|
||||||
|
|
||||||
|
--color-secondary: hsl(var(--secondary));
|
||||||
|
--color-secondary-foreground: hsl(var(--secondary-foreground));
|
||||||
|
|
||||||
|
--color-destructive: hsl(var(--destructive));
|
||||||
|
--color-destructive-foreground: hsl(var(--destructive-foreground));
|
||||||
|
|
||||||
|
--color-muted: hsl(var(--muted));
|
||||||
|
--color-muted-foreground: hsl(var(--muted-foreground));
|
||||||
|
|
||||||
|
--color-accent: hsl(var(--accent));
|
||||||
|
--color-accent-foreground: hsl(var(--accent-foreground));
|
||||||
|
|
||||||
|
--color-popover: hsl(var(--popover));
|
||||||
|
--color-popover-foreground: hsl(var(--popover-foreground));
|
||||||
|
|
||||||
|
--color-card: hsl(var(--card));
|
||||||
|
--color-card-foreground: hsl(var(--card-foreground));
|
||||||
|
|
||||||
|
--radius-lg: var(--radius);
|
||||||
|
--radius-md: calc(var(--radius) - 2px);
|
||||||
|
--radius-sm: calc(var(--radius) - 4px);
|
||||||
|
|
||||||
|
--font-sans:
|
||||||
|
IBM Plex Sans, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI,
|
||||||
|
Roboto, sans-serif;
|
||||||
|
--font-heading: Bricolage Grotesque, system-ui, sans-serif;
|
||||||
|
--font-label: JetBrains Mono, monospace;
|
||||||
|
|
||||||
|
--background-image-gradient-brand: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
#06b6d4 0%,
|
||||||
|
#22d3ee 100%
|
||||||
|
);
|
||||||
|
--background-image-gradient-brand-hover: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
#0891b2 0%,
|
||||||
|
#06b6d4 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
The default border color has changed to `currentcolor` in Tailwind CSS v4,
|
||||||
|
so we've added these compatibility styles to make sure everything still
|
||||||
|
looks the same as it did with Tailwind CSS v3.
|
||||||
|
|
||||||
|
If we ever want to remove these styles, we need to add an explicit border
|
||||||
|
color utility to any element that depends on these defaults.
|
||||||
|
*/
|
||||||
|
@layer base {
|
||||||
|
*,
|
||||||
|
::after,
|
||||||
|
::before,
|
||||||
|
::backdrop,
|
||||||
|
::file-selector-button {
|
||||||
|
border-color: var(--color-gray-200, currentcolor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility animate-fade-in {
|
||||||
|
animation: fade-in 200ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility animate-fade-in-up {
|
||||||
|
animation: fade-in-up 200ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility animate-slide-in-left {
|
||||||
|
animation: slide-in-from-left 200ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility animate-slide-in-bottom {
|
||||||
|
animation: slide-in-from-bottom 200ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility animate-scale-in {
|
||||||
|
animation: scale-in 150ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility btn-press {
|
||||||
|
/* Button press feedback */
|
||||||
|
@apply active:scale-[0.98] transition-transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility text-gradient-brand {
|
||||||
|
/* Brand gradient text */
|
||||||
|
@apply bg-gradient-brand bg-clip-text text-transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility glass-card {
|
||||||
|
/* Glass card — interactive with hover lift */
|
||||||
|
background: var(--glass-bg);
|
||||||
|
backdrop-filter: var(--glass-blur);
|
||||||
|
-webkit-backdrop-filter: var(--glass-blur);
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: var(--shadow-float);
|
||||||
|
transition:
|
||||||
|
transform 200ms var(--ease-out-smooth),
|
||||||
|
border-color 200ms var(--ease-out-smooth),
|
||||||
|
box-shadow 200ms var(--ease-out-smooth);
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.02);
|
||||||
|
border-color: var(--glass-border-hover);
|
||||||
|
box-shadow: var(--shadow-float-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility glass-card-static {
|
||||||
|
/* Glass card — static, no hover transform */
|
||||||
|
background: var(--glass-bg);
|
||||||
|
backdrop-filter: var(--glass-blur);
|
||||||
|
-webkit-backdrop-filter: var(--glass-blur);
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: var(--shadow-float);
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility active-glow {
|
||||||
|
/* Breathing glow for highlighted stat cards */
|
||||||
|
animation: breatheGlow 3s ease-in-out infinite alternate;
|
||||||
|
}
|
||||||
|
@utility rdp-custom {
|
||||||
|
@apply text-foreground;
|
||||||
|
|
||||||
|
& .rdp-month {
|
||||||
|
@apply w-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .rdp-caption {
|
||||||
|
@apply flex justify-center items-center mb-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .rdp-caption_label {
|
||||||
|
@apply text-sm font-medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .rdp-nav {
|
||||||
|
@apply flex gap-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .rdp-nav_button {
|
||||||
|
@apply h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .rdp-table {
|
||||||
|
@apply w-full border-collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .rdp-head_cell {
|
||||||
|
@apply text-muted-foreground font-normal text-xs;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .rdp-cell {
|
||||||
|
@apply text-center text-sm p-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .rdp-day {
|
||||||
|
@apply h-9 w-9 p-0 font-normal hover:bg-accent rounded-md transition-colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .rdp-day_selected {
|
||||||
|
@apply bg-primary text-primary-foreground hover:bg-primary/90;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .rdp-day_today {
|
||||||
|
@apply bg-accent text-accent-foreground;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .rdp-day_outside {
|
||||||
|
@apply text-muted-foreground opacity-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .rdp-day_disabled {
|
||||||
|
@apply text-muted-foreground opacity-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .rdp-day_range_middle {
|
||||||
|
@apply bg-accent text-accent-foreground;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .rdp-day_hidden {
|
||||||
|
@apply invisible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@utility rdp-month {
|
||||||
|
.rdp-custom & {
|
||||||
|
@apply w-full;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@utility rdp-caption {
|
||||||
|
.rdp-custom & {
|
||||||
|
@apply flex justify-center items-center mb-4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@utility rdp-caption_label {
|
||||||
|
.rdp-custom & {
|
||||||
|
@apply text-sm font-medium;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@utility rdp-nav {
|
||||||
|
.rdp-custom & {
|
||||||
|
@apply flex gap-1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@utility rdp-nav_button {
|
||||||
|
.rdp-custom & {
|
||||||
|
@apply h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@utility rdp-table {
|
||||||
|
.rdp-custom & {
|
||||||
|
@apply w-full border-collapse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@utility rdp-head_cell {
|
||||||
|
.rdp-custom & {
|
||||||
|
@apply text-muted-foreground font-normal text-xs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@utility rdp-cell {
|
||||||
|
.rdp-custom & {
|
||||||
|
@apply text-center text-sm p-0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@utility rdp-day {
|
||||||
|
.rdp-custom & {
|
||||||
|
@apply h-9 w-9 p-0 font-normal hover:bg-accent rounded-md transition-colors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@utility rdp-day_selected {
|
||||||
|
.rdp-custom & {
|
||||||
|
@apply bg-primary text-primary-foreground hover:bg-primary/90;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@utility rdp-day_today {
|
||||||
|
.rdp-custom & {
|
||||||
|
@apply bg-accent text-accent-foreground;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@utility rdp-day_outside {
|
||||||
|
.rdp-custom & {
|
||||||
|
@apply text-muted-foreground opacity-50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@utility rdp-day_disabled {
|
||||||
|
.rdp-custom & {
|
||||||
|
@apply text-muted-foreground opacity-50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@utility rdp-day_range_middle {
|
||||||
|
.rdp-custom & {
|
||||||
|
@apply bg-accent text-accent-foreground;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@utility rdp-day_hidden {
|
||||||
|
.rdp-custom & {
|
||||||
|
@apply invisible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
@@ -86,289 +369,233 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* App Shell Grid Layout */
|
@layer utilities {
|
||||||
.app-shell {
|
/* App Shell Grid Layout */
|
||||||
display: grid;
|
|
||||||
grid-template-columns: var(--sidebar-w) 1fr;
|
|
||||||
grid-template-rows: 56px 1fr;
|
|
||||||
height: 100vh;
|
|
||||||
overflow: hidden;
|
|
||||||
transition: grid-template-columns 200ms ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-shell--collapsed {
|
|
||||||
grid-template-columns: 56px 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topbar {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar {
|
|
||||||
min-height: 0;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-content {
|
|
||||||
min-height: 0;
|
|
||||||
min-width: 0;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mobile: single column */
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
.app-shell {
|
.app-shell {
|
||||||
grid-template-columns: 1fr;
|
display: grid;
|
||||||
|
grid-template-columns: var(--sidebar-w) 1fr;
|
||||||
|
grid-template-rows: 56px 1fr;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: grid-template-columns 200ms ease;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* Staggered fade-in for page sections */
|
.app-shell--collapsed {
|
||||||
.fade-in {
|
grid-template-columns: 56px 1fr;
|
||||||
animation: fadeIn 0.3s ease forwards;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
.topbar {
|
||||||
from { opacity: 0; transform: translateY(6px); }
|
grid-column: 1 / -1;
|
||||||
to { opacity: 1; transform: translateY(0); }
|
}
|
||||||
}
|
|
||||||
/* Animations */
|
|
||||||
@keyframes fade-in {
|
|
||||||
from { opacity: 0; }
|
|
||||||
to { opacity: 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fade-in-up {
|
.sidebar {
|
||||||
from { opacity: 0; transform: translateY(4px); }
|
min-height: 0;
|
||||||
to { opacity: 1; transform: translateY(0); }
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes slide-in-from-left {
|
.main-content {
|
||||||
from { transform: translateX(-100%); }
|
min-height: 0;
|
||||||
to { transform: translateX(0); }
|
min-width: 0;
|
||||||
}
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes slide-in-from-bottom {
|
/* Mobile: single column */
|
||||||
from { opacity: 0; transform: translateY(16px); }
|
@media (max-width: 767px) {
|
||||||
to { opacity: 1; transform: translateY(0); }
|
.app-shell {
|
||||||
}
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes scale-in {
|
/* Staggered fade-in for page sections */
|
||||||
from { opacity: 0; transform: scale(0.95); }
|
.fade-in {
|
||||||
to { opacity: 1; transform: scale(1); }
|
animation: fadeIn 0.3s ease forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes slideDown {
|
@keyframes fadeIn {
|
||||||
from { transform: translateY(-100%); opacity: 0; }
|
from {
|
||||||
to { transform: translateY(0); opacity: 1; }
|
opacity: 0;
|
||||||
}
|
transform: translateY(6px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Animations */
|
||||||
|
@keyframes fade-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes slideInRight {
|
@keyframes fade-in-up {
|
||||||
from { transform: translateX(100%); }
|
from {
|
||||||
to { transform: translateX(0); }
|
opacity: 0;
|
||||||
}
|
transform: translateY(4px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes fadeInRight {
|
@keyframes slide-in-from-left {
|
||||||
from { transform: translateX(30px); opacity: 0; }
|
from {
|
||||||
to { transform: translateX(0); opacity: 1; }
|
transform: translateX(-100%);
|
||||||
}
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes breatheGlow {
|
@keyframes slide-in-from-bottom {
|
||||||
from { box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), 0 0 20px rgba(6, 182, 212, 0.04); }
|
from {
|
||||||
to { box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), 0 0 30px rgba(6, 182, 212, 0.12); }
|
opacity: 0;
|
||||||
}
|
transform: translateY(16px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes bellWobble {
|
@keyframes scale-in {
|
||||||
0% { transform: rotate(0deg); }
|
from {
|
||||||
20% { transform: rotate(8deg); }
|
opacity: 0;
|
||||||
40% { transform: rotate(-6deg); }
|
transform: scale(0.95);
|
||||||
60% { transform: rotate(4deg); }
|
}
|
||||||
80% { transform: rotate(-2deg); }
|
to {
|
||||||
100% { transform: rotate(0deg); }
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideDown {
|
||||||
|
from {
|
||||||
|
transform: translateY(-100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInRight {
|
||||||
|
from {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInRight {
|
||||||
|
from {
|
||||||
|
transform: translateX(30px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes breatheGlow {
|
||||||
|
from {
|
||||||
|
box-shadow:
|
||||||
|
0 8px 32px rgba(0, 0, 0, 0.3),
|
||||||
|
0 0 20px rgba(6, 182, 212, 0.04);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
box-shadow:
|
||||||
|
0 8px 32px rgba(0, 0, 0, 0.3),
|
||||||
|
0 0 30px rgba(6, 182, 212, 0.12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bellWobble {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
transform: rotate(8deg);
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
transform: rotate(-6deg);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: rotate(4deg);
|
||||||
|
}
|
||||||
|
80% {
|
||||||
|
transform: rotate(-2deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
.animate-fade-in {
|
/* Sonner Toast Customization — outside @layer for higher specificity */
|
||||||
animation: fade-in 200ms ease-out;
|
[data-sonner-toast] {
|
||||||
|
background-color: hsl(var(--card)) !important;
|
||||||
|
color: hsl(var(--card-foreground)) !important;
|
||||||
|
border: 1px solid hsl(var(--border)) !important;
|
||||||
|
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.3) !important;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
font-family: 'IBM Plex Sans', system-ui, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.animate-fade-in-up {
|
[data-sonner-toast] [data-title] {
|
||||||
animation: fade-in-up 200ms ease-out;
|
font-family: 'IBM Plex Sans', system-ui, sans-serif;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.animate-slide-in-left {
|
[data-sonner-toast][data-type='success'] {
|
||||||
animation: slide-in-from-left 200ms ease-out;
|
border-color: rgba(52, 211, 153, 0.3) !important;
|
||||||
|
}
|
||||||
|
[data-sonner-toast][data-type='success'] [data-icon] {
|
||||||
|
color: #34d399;
|
||||||
}
|
}
|
||||||
|
|
||||||
.animate-slide-in-bottom {
|
[data-sonner-toast][data-type='error'] {
|
||||||
animation: slide-in-from-bottom 200ms ease-out;
|
border-color: rgba(248, 113, 113, 0.3) !important;
|
||||||
|
}
|
||||||
|
[data-sonner-toast][data-type='error'] [data-icon] {
|
||||||
|
color: #f87171;
|
||||||
}
|
}
|
||||||
|
|
||||||
.animate-scale-in {
|
[data-sonner-toast][data-type='info'] {
|
||||||
animation: scale-in 150ms ease-out;
|
border-color: hsl(var(--border)) !important;
|
||||||
|
}
|
||||||
|
[data-sonner-toast][data-type='info'] [data-icon] {
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Button press feedback */
|
[data-sonner-toast][data-type='warning'] {
|
||||||
.btn-press {
|
border-color: rgba(251, 191, 36, 0.3) !important;
|
||||||
@apply active:scale-[0.98] transition-transform;
|
}
|
||||||
|
[data-sonner-toast][data-type='warning'] [data-icon] {
|
||||||
|
color: #fbbf24;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Brand gradient text */
|
[data-sonner-toast] [data-close-button] {
|
||||||
.text-gradient-brand {
|
color: hsl(var(--muted-foreground));
|
||||||
@apply bg-gradient-brand bg-clip-text text-transparent;
|
border-radius: 0.375rem;
|
||||||
|
transition:
|
||||||
|
color 150ms,
|
||||||
|
background-color 150ms;
|
||||||
|
}
|
||||||
|
[data-sonner-toast] [data-close-button]:hover {
|
||||||
|
background-color: hsl(var(--accent));
|
||||||
|
color: hsl(var(--accent-foreground));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Glass card — interactive with hover lift */
|
/* React Day Picker Customization */
|
||||||
.glass-card {
|
|
||||||
background: var(--glass-bg);
|
|
||||||
backdrop-filter: var(--glass-blur);
|
|
||||||
-webkit-backdrop-filter: var(--glass-blur);
|
|
||||||
border: 1px solid var(--glass-border);
|
|
||||||
border-radius: 16px;
|
|
||||||
box-shadow: var(--shadow-float);
|
|
||||||
transition: transform 200ms var(--ease-out-smooth),
|
|
||||||
border-color 200ms var(--ease-out-smooth),
|
|
||||||
box-shadow 200ms var(--ease-out-smooth);
|
|
||||||
}
|
|
||||||
.glass-card:hover {
|
|
||||||
transform: scale(1.02);
|
|
||||||
border-color: var(--glass-border-hover);
|
|
||||||
box-shadow: var(--shadow-float-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Glass card — static, no hover transform */
|
|
||||||
.glass-card-static {
|
|
||||||
background: var(--glass-bg);
|
|
||||||
backdrop-filter: var(--glass-blur);
|
|
||||||
-webkit-backdrop-filter: var(--glass-blur);
|
|
||||||
border: 1px solid var(--glass-border);
|
|
||||||
border-radius: 16px;
|
|
||||||
box-shadow: var(--shadow-float);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Breathing glow for highlighted stat cards */
|
|
||||||
.active-glow {
|
|
||||||
animation: breatheGlow 3s ease-in-out infinite alternate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sonner Toast Customization — outside @layer for higher specificity */
|
|
||||||
[data-sonner-toast] {
|
|
||||||
background-color: hsl(var(--card)) !important;
|
|
||||||
color: hsl(var(--card-foreground)) !important;
|
|
||||||
border: 1px solid hsl(var(--border)) !important;
|
|
||||||
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.3) !important;
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
font-family: 'IBM Plex Sans', system-ui, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-sonner-toast] [data-title] {
|
|
||||||
font-family: 'IBM Plex Sans', system-ui, sans-serif;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-sonner-toast][data-type="success"] {
|
|
||||||
border-color: rgba(52, 211, 153, 0.3) !important;
|
|
||||||
}
|
|
||||||
[data-sonner-toast][data-type="success"] [data-icon] {
|
|
||||||
color: #34d399;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-sonner-toast][data-type="error"] {
|
|
||||||
border-color: rgba(248, 113, 113, 0.3) !important;
|
|
||||||
}
|
|
||||||
[data-sonner-toast][data-type="error"] [data-icon] {
|
|
||||||
color: #f87171;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-sonner-toast][data-type="info"] {
|
|
||||||
border-color: hsl(var(--border)) !important;
|
|
||||||
}
|
|
||||||
[data-sonner-toast][data-type="info"] [data-icon] {
|
|
||||||
color: hsl(var(--muted-foreground));
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-sonner-toast][data-type="warning"] {
|
|
||||||
border-color: rgba(251, 191, 36, 0.3) !important;
|
|
||||||
}
|
|
||||||
[data-sonner-toast][data-type="warning"] [data-icon] {
|
|
||||||
color: #fbbf24;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-sonner-toast] [data-close-button] {
|
|
||||||
color: hsl(var(--muted-foreground));
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
transition: color 150ms, background-color 150ms;
|
|
||||||
}
|
|
||||||
[data-sonner-toast] [data-close-button]:hover {
|
|
||||||
background-color: hsl(var(--accent));
|
|
||||||
color: hsl(var(--accent-foreground));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* React Day Picker Customization */
|
|
||||||
@layer components {
|
|
||||||
.rdp-custom {
|
|
||||||
@apply text-foreground;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdp-custom .rdp-month {
|
|
||||||
@apply w-full;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdp-custom .rdp-caption {
|
|
||||||
@apply flex justify-center items-center mb-4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdp-custom .rdp-caption_label {
|
|
||||||
@apply text-sm font-medium;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdp-custom .rdp-nav {
|
|
||||||
@apply flex gap-1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdp-custom .rdp-nav_button {
|
|
||||||
@apply h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdp-custom .rdp-table {
|
|
||||||
@apply w-full border-collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdp-custom .rdp-head_cell {
|
|
||||||
@apply text-muted-foreground font-normal text-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdp-custom .rdp-cell {
|
|
||||||
@apply text-center text-sm p-0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdp-custom .rdp-day {
|
|
||||||
@apply h-9 w-9 p-0 font-normal hover:bg-accent rounded-md transition-colors;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdp-custom .rdp-day_selected {
|
|
||||||
@apply bg-primary text-primary-foreground hover:bg-primary/90;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdp-custom .rdp-day_today {
|
|
||||||
@apply bg-accent text-accent-foreground;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdp-custom .rdp-day_outside {
|
|
||||||
@apply text-muted-foreground opacity-50;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdp-custom .rdp-day_disabled {
|
|
||||||
@apply text-muted-foreground opacity-50;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdp-custom .rdp-day_range_middle {
|
|
||||||
@apply bg-accent text-accent-foreground;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rdp-custom .rdp-day_hidden {
|
|
||||||
@apply invisible;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* React Flow dark theme overrides */
|
/* React Flow dark theme overrides */
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ export function AccountSettingsPage() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex-1 rounded-md border border-border bg-card px-3 py-2',
|
'flex-1 rounded-md border border-border bg-card px-3 py-2',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
autoFocus
|
autoFocus
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
@@ -367,7 +367,7 @@ export function AccountSettingsPage() {
|
|||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border bg-card px-2 py-0.5 text-xs',
|
'rounded-md border border-border bg-card px-2 py-0.5 text-xs',
|
||||||
'text-foreground focus:border-primary focus:outline-none'
|
'text-foreground focus:border-primary focus:outline-hidden'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<option value="engineer">engineer</option>
|
<option value="engineer">engineer</option>
|
||||||
@@ -415,7 +415,7 @@ export function AccountSettingsPage() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex-1 rounded-md border border-border bg-card px-3 py-2',
|
'flex-1 rounded-md border border-border bg-card px-3 py-2',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<select
|
<select
|
||||||
@@ -423,7 +423,7 @@ export function AccountSettingsPage() {
|
|||||||
onChange={(e) => setInviteRole(e.target.value)}
|
onChange={(e) => setInviteRole(e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border bg-card px-3 py-2',
|
'rounded-md border border-border bg-card px-3 py-2',
|
||||||
'text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
|
'text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<option value="engineer">Engineer</option>
|
<option value="engineer">Engineer</option>
|
||||||
@@ -609,7 +609,7 @@ export function AccountSettingsPage() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'mt-2 block w-full max-w-xs rounded-xl border border-border bg-card px-3 py-2',
|
'mt-2 block w-full max-w-xs rounded-xl border border-border bg-card px-3 py-2',
|
||||||
'text-sm text-foreground',
|
'text-sm text-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<option value="markdown">Markdown (.md)</option>
|
<option value="markdown">Markdown (.md)</option>
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ export default function AssistantChatPage() {
|
|||||||
<div className="w-8 h-8 rounded-full bg-primary/15 flex items-center justify-center">
|
<div className="w-8 h-8 rounded-full bg-primary/15 flex items-center justify-center">
|
||||||
<Sparkles size={14} className="text-primary" />
|
<Sparkles size={14} className="text-primary" />
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] rounded-2xl px-4 py-3">
|
<div className="bg-[rgba(255,255,255,0.04)] border border-brand-border rounded-2xl px-4 py-3">
|
||||||
<Loader2 size={16} className="animate-spin text-primary" />
|
<Loader2 size={16} className="animate-spin text-primary" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -242,7 +242,7 @@ export default function AssistantChatPage() {
|
|||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
placeholder="Ask about IT, networking, troubleshooting..."
|
placeholder="Ask about IT, networking, troubleshooting..."
|
||||||
rows={3}
|
rows={3}
|
||||||
className="flex-1 resize-none rounded-xl border bg-card text-foreground text-sm placeholder:text-muted-foreground px-4 py-3 focus:outline-none focus:border-[rgba(6,182,212,0.3)]"
|
className="flex-1 resize-none rounded-xl border bg-card text-foreground text-sm placeholder:text-muted-foreground px-4 py-3 focus:outline-hidden focus:border-[rgba(6,182,212,0.3)]"
|
||||||
style={{ borderColor: 'var(--glass-border)' }}
|
style={{ borderColor: 'var(--glass-border)' }}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
/>
|
/>
|
||||||
@@ -250,7 +250,7 @@ export default function AssistantChatPage() {
|
|||||||
<button
|
<button
|
||||||
onClick={handleSend}
|
onClick={handleSend}
|
||||||
disabled={!input.trim() || loading}
|
disabled={!input.trim() || loading}
|
||||||
className="bg-gradient-brand text-[#101114] p-3 rounded-xl hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-40"
|
className="bg-gradient-brand text-brand-dark p-3 rounded-xl hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-40"
|
||||||
title="Send message"
|
title="Send message"
|
||||||
>
|
>
|
||||||
<Send size={18} />
|
<Send size={18} />
|
||||||
@@ -286,7 +286,7 @@ export default function AssistantChatPage() {
|
|||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
onClick={handleNewChat}
|
onClick={handleNewChat}
|
||||||
className="bg-gradient-brand text-[#101114] font-semibold text-sm rounded-[10px] px-6 py-2.5 hover:opacity-90 active:scale-[0.97] transition-all"
|
className="bg-gradient-brand text-brand-dark font-semibold text-sm rounded-[10px] px-6 py-2.5 hover:opacity-90 active:scale-[0.97] transition-all"
|
||||||
>
|
>
|
||||||
Start a Conversation
|
Start a Conversation
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export function ChangePasswordPage() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'block w-full rounded-xl border border-border bg-card px-3 py-2',
|
'block w-full rounded-xl border border-border bg-card px-3 py-2',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'transition-colors'
|
'transition-colors'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -116,7 +116,7 @@ export function ChangePasswordPage() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'block w-full rounded-xl border border-border bg-card px-3 py-2',
|
'block w-full rounded-xl border border-border bg-card px-3 py-2',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'transition-colors'
|
'transition-colors'
|
||||||
)}
|
)}
|
||||||
placeholder="At least 10 characters"
|
placeholder="At least 10 characters"
|
||||||
@@ -139,7 +139,7 @@ export function ChangePasswordPage() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'block w-full rounded-xl border border-border bg-card px-3 py-2',
|
'block w-full rounded-xl border border-border bg-card px-3 py-2',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'transition-colors'
|
'transition-colors'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -151,7 +151,7 @@ export function ChangePasswordPage() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-xl px-4 py-2.5 text-sm font-semibold btn-press',
|
'w-full rounded-xl px-4 py-2.5 text-sm font-semibold btn-press',
|
||||||
'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90',
|
'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90',
|
||||||
'focus:outline-none focus:ring-2 focus:ring-primary/30 focus:ring-offset-2 focus:ring-offset-black',
|
'focus:outline-hidden focus:ring-2 focus:ring-primary/30 focus:ring-offset-2 focus:ring-offset-black',
|
||||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
'transition-all'
|
'transition-all'
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ export function FeedbackPage() {
|
|||||||
onChange={e => setEmail(e.target.value)}
|
onChange={e => setEmail(e.target.value)}
|
||||||
placeholder="your@email.com"
|
placeholder="your@email.com"
|
||||||
required
|
required
|
||||||
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground placeholder:text-muted-foreground focus:border-primary focus:ring-1 focus:ring-primary/20 focus:outline-none"
|
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground placeholder:text-muted-foreground focus:border-primary focus:ring-1 focus:ring-primary/20 focus:outline-hidden"
|
||||||
/>
|
/>
|
||||||
<p className="mt-1 text-xs text-muted-foreground">We'll reply to this address if we need more details.</p>
|
<p className="mt-1 text-xs text-muted-foreground">We'll reply to this address if we need more details.</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -193,7 +193,7 @@ export function FeedbackPage() {
|
|||||||
onClick={() => setTypeDropdownOpen(!typeDropdownOpen)}
|
onClick={() => setTypeDropdownOpen(!typeDropdownOpen)}
|
||||||
onKeyDown={handleDropdownKeyDown}
|
onKeyDown={handleDropdownKeyDown}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-full rounded-lg border border-border bg-card px-3 py-2 text-left flex items-center justify-between focus:border-primary focus:ring-1 focus:ring-primary/20 focus:outline-none",
|
"w-full rounded-lg border border-border bg-card px-3 py-2 text-left flex items-center justify-between focus:border-primary focus:ring-1 focus:ring-primary/20 focus:outline-hidden",
|
||||||
feedbackType ? "text-foreground" : "text-muted-foreground"
|
feedbackType ? "text-foreground" : "text-muted-foreground"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -244,7 +244,7 @@ export function FeedbackPage() {
|
|||||||
required
|
required
|
||||||
minLength={10}
|
minLength={10}
|
||||||
rows={6}
|
rows={6}
|
||||||
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground placeholder:text-muted-foreground focus:border-primary focus:ring-1 focus:ring-primary/20 focus:outline-none resize-y"
|
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground placeholder:text-muted-foreground focus:border-primary focus:ring-1 focus:ring-primary/20 focus:outline-hidden resize-y"
|
||||||
/>
|
/>
|
||||||
<p className="mt-1 text-xs text-muted-foreground">
|
<p className="mt-1 text-xs text-muted-foreground">
|
||||||
{message.trim().length < 10
|
{message.trim().length < 10
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export function ForgotPasswordPage() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'block w-full rounded-xl border border-border bg-card px-3 py-2',
|
'block w-full rounded-xl border border-border bg-card px-3 py-2',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'transition-colors'
|
'transition-colors'
|
||||||
)}
|
)}
|
||||||
placeholder="you@example.com"
|
placeholder="you@example.com"
|
||||||
@@ -88,7 +88,7 @@ export function ForgotPasswordPage() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-xl px-4 py-2.5 text-sm font-semibold btn-press',
|
'w-full rounded-xl px-4 py-2.5 text-sm font-semibold btn-press',
|
||||||
'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90',
|
'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90',
|
||||||
'focus:outline-none focus:ring-2 focus:ring-primary/30 focus:ring-offset-2 focus:ring-offset-background',
|
'focus:outline-hidden focus:ring-2 focus:ring-primary/30 focus:ring-offset-2 focus:ring-offset-background',
|
||||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
'transition-all'
|
'transition-all'
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export default function GuideDetailPage() {
|
|||||||
<p className="text-sm text-muted-foreground mb-4">The guide you're looking for doesn't exist.</p>
|
<p className="text-sm text-muted-foreground mb-4">The guide you're looking for doesn't exist.</p>
|
||||||
<Link
|
<Link
|
||||||
to="/guides"
|
to="/guides"
|
||||||
className="bg-gradient-brand text-[#101114] font-semibold text-sm rounded-[10px] px-5 py-2 hover:opacity-90 active:scale-[0.97] transition-all"
|
className="bg-gradient-brand text-brand-dark font-semibold text-sm rounded-[10px] px-5 py-2 hover:opacity-90 active:scale-[0.97] transition-all"
|
||||||
>
|
>
|
||||||
Back to Guides
|
Back to Guides
|
||||||
</Link>
|
</Link>
|
||||||
@@ -47,10 +47,10 @@ export default function GuideDetailPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-4 mt-4 pt-4 border-t" style={{ borderColor: 'var(--glass-border)' }}>
|
<div className="flex items-center gap-4 mt-4 pt-4 border-t" style={{ borderColor: 'var(--glass-border)' }}>
|
||||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
|
||||||
{guide.sections.length} {guide.sections.length === 1 ? 'section' : 'sections'}
|
{guide.sections.length} {guide.sections.length === 1 ? 'section' : 'sections'}
|
||||||
</span>
|
</span>
|
||||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
|
||||||
{guide.sections.reduce((acc, s) => acc + s.steps.length, 0)} steps
|
{guide.sections.reduce((acc, s) => acc + s.steps.length, 0)} steps
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ export function LoginPage() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'block w-full rounded-[10px] border border-border bg-card px-3 py-2.5',
|
'block w-full rounded-[10px] border border-border bg-card px-3 py-2.5',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-[rgba(6,182,212,0.3)] focus:outline-none focus:ring-1 focus:ring-primary/20',
|
'focus:border-[rgba(6,182,212,0.3)] focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'transition-colors'
|
'transition-colors'
|
||||||
)}
|
)}
|
||||||
placeholder="you@example.com"
|
placeholder="you@example.com"
|
||||||
@@ -127,7 +127,7 @@ export function LoginPage() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'block w-full rounded-[10px] border border-border bg-card px-3 py-2.5',
|
'block w-full rounded-[10px] border border-border bg-card px-3 py-2.5',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-foreground placeholder:text-muted-foreground',
|
||||||
'focus:border-[rgba(6,182,212,0.3)] focus:outline-none focus:ring-1 focus:ring-primary/20',
|
'focus:border-[rgba(6,182,212,0.3)] focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'transition-colors'
|
'transition-colors'
|
||||||
)}
|
)}
|
||||||
placeholder="••••••••••"
|
placeholder="••••••••••"
|
||||||
@@ -145,8 +145,8 @@ export function LoginPage() {
|
|||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-[10px] px-4 py-2.5 text-sm font-semibold',
|
'w-full rounded-[10px] px-4 py-2.5 text-sm font-semibold',
|
||||||
'bg-gradient-brand text-[#101114] shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97]',
|
'bg-gradient-brand text-brand-dark shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97]',
|
||||||
'focus:outline-none focus:ring-2 focus:ring-primary/30 focus:ring-offset-2 focus:ring-offset-background',
|
'focus:outline-hidden focus:ring-2 focus:ring-primary/30 focus:ring-offset-2 focus:ring-offset-background',
|
||||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
'transition-all'
|
'transition-all'
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export default function MyAnalyticsPage() {
|
|||||||
<select
|
<select
|
||||||
value={period}
|
value={period}
|
||||||
onChange={(e) => setPeriod(e.target.value as AnalyticsPeriod)}
|
onChange={(e) => setPeriod(e.target.value as AnalyticsPeriod)}
|
||||||
className="rounded-lg border border-border bg-card px-3 py-1.5 text-sm text-foreground focus:outline-none focus:ring-1 focus:ring-primary/20"
|
className="rounded-lg border border-border bg-card px-3 py-1.5 text-sm text-foreground focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
{PERIOD_OPTIONS.map((opt) => (
|
{PERIOD_OPTIONS.map((opt) => (
|
||||||
<option key={opt.value} value={opt.value}>
|
<option key={opt.value} value={opt.value}>
|
||||||
@@ -298,7 +298,7 @@ export default function MyAnalyticsPage() {
|
|||||||
<div className="flex items-center justify-between mb-1">
|
<div className="flex items-center justify-between mb-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div
|
<div
|
||||||
className="h-2.5 w-2.5 rounded-full flex-shrink-0"
|
className="h-2.5 w-2.5 rounded-full shrink-0"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
OUTCOME_COLORS[outcome] ?? '#94a3b8',
|
OUTCOME_COLORS[outcome] ?? '#94a3b8',
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ export function MyTreesPage() {
|
|||||||
{showCreateMenu && (
|
{showCreateMenu && (
|
||||||
<>
|
<>
|
||||||
<div className="fixed inset-0 z-10" onClick={() => setShowCreateMenu(false)} />
|
<div className="fixed inset-0 z-10" onClick={() => setShowCreateMenu(false)} />
|
||||||
<div className="absolute right-0 z-20 mt-1 w-56 rounded-lg border border-border bg-card p-1 shadow-xl backdrop-blur-sm">
|
<div className="absolute right-0 z-20 mt-1 w-56 rounded-lg border border-border bg-card p-1 shadow-xl backdrop-blur-xs">
|
||||||
<Link
|
<Link
|
||||||
to="/trees/new"
|
to="/trees/new"
|
||||||
onClick={() => setShowCreateMenu(false)}
|
onClick={() => setShowCreateMenu(false)}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user