8000 build(deps): move to Tailwind CSS 4 by adeelahmad · Pull Request #377 · adeelahmad/lense · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/frontend.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ four-colour loop (Transcribe blue, Diarize red, Analyze green, Summarize and Rep
* **Tokens** live in `app/styles/tokens.css` and are mapped into Tailwind (`bg-surface`, `text-fg-secondary`,
`border-blue-border`, `spk-1…8`, `emo-*`, `rounded-pill`, `shadow-2`...). Don't hard-code colours: the dark theme
switches by variables (`[data-theme=dark]`, or the system preference).
* **Tailwind 4** is configured in CSS: the `@theme` block in `app/globals.css` maps the tokens, and there is no
`tailwind.config.js`. Its utilities stay outside cascade layers there, so a class still beats the notes editor's
global element styles (BlockSuite adds rules such as `input { font-size: 12px }` to the page).
* **Type:** DM Sans for the UI (13–14 px), Source Serif 4 for transcripts (`.transcript-text`, 17.5/1.6), JetBrains
Mono for paths and code. All three are self-hosted in `public/fonts`.
* **Primitives** (`components/ui`): Button (pill; `disabledReason` explains a disabled action), IconButton, Badge,
Expand Down
13 changes: 13 additions & 0 deletions nextjs-frontend/__tests__/globalsCss.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { readFileSync } from "fs";
import { join } from "path";

const css = readFileSync(join(__dirname, "..", "app", "globals.css"), "utf8");

describe("the app's stylesheet", () => {
it("keeps Tailwind's utilities out of cascade layers, so a class beats the note editor's element rules", () => {
// BlockSuite adds `input { font-size: 12px }` and the like to the page, unlayered: a layered utility would lose to it
expect(css).toMatch(/@import "tailwindcss\/utilities\.css";/);
expect(css).not.toMatch(/@import "tailwindcss";/);
expect(css).not.toMatch(/@layer utilities/);
});
});
218 changes: 170 additions & 48 deletions nextjs-frontend/app/globals.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,159 @@
@import "./styles/fonts.css";
@import "./styles/tokens.css";

@tailwind base;
@tailwind components;
@tailwind utilities;
/* Tailwind 4 puts utilities in a cascade layer, where any unlayered rule beats them. The note editor (BlockSuite)
injects unlayered global rules such as `input { font-size: 12px }`, so utilities stay unlayered here, as in Tailwind 3,
and a class still wins over a bare element rule. */
@layer theme, base, components;
@import "tailwindcss/theme.cs 10BC8 s" layer(theme);
@import "tailwindcss/preflight.css" layer(base);
@import "tailwindcss/utilities.css";

@plugin "tailwindcss-animate";

@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));

@theme {
--font-sans: DM Sans, ui-sans-serif, system-ui, sans-serif;
--font-serif: "Source Serif 4", Georgia, serif;
--font-mono: JetBrains Mono, ui-monospace, Menlo, monospace;

--color-background: var(--background);

--color-surface: var(--surface);
--color-surface-neutral: var(--surface-neutral);

--color-border: var(--border);

--color-fg: var(--text-primary);
--color-fg-strong: var(--text-strong);
--color-fg-secondary: var(--text-secondary);
--color-fg-muted: var(--text-muted);
--color-fg-accent: var(--accent-text);
--color-fg-on-color: var(--text-on-color);

--color-blue: var(--aladdin-blue);
--color-blue-dark: var(--blue-dark);
--color-blue-surface: var(--intent-surface);
--color-blue-border: var(--intent-border);

--color-red: var(--aladdin-red);
--color-red-dark: var(--red-dark);
--color-red-surface: var(--red-surface);
--color-red-border: var(--red-border);

--color-green: var(--aladdin-green);
--color-green-dark: var(--green-dark);
--color-green-surface: var(--green-surface);
--color-green-border: var(--green-border);

--color-gold: var(--aladdin-gold);
--color-gold-dark: var(--gold-dark);
--color-gold-surface: var(--gate-surface);
--color-gold-border: var(--gate-border);

--color-hl: var(--hl);
--color-hl-word: var(--hl-word);

--color-spk-1: var(--spk-1);
--color-spk-2: var(--spk-2);
--color-spk-3: var(--spk-3);
--color-spk-4: var(--spk-4);
--color-spk-5: var(--spk-5);
--color-spk-6: var(--spk-6);
--color-spk-7: var(--spk-7);
--color-spk-8: var(--spk-8);

--color-emo-neutral: var(--emo-neutral);
--color-emo-happy: var(--emo-happy);
--color-emo-amused: var(--emo-amused);
--color-emo-surprise: var(--emo-surprise);
--color-emo-sad: var(--emo-sad);
--color-emo-angry: var(--emo-angry);
--color-emo-fear: var(--emo-fear);
--color-emo-disgust: var(--emo-disgust);

--color-term-bg: var(--term-bg);
--color-term-fg: var(--term-fg);
--color-term-muted: var(--term-muted);

--radius-xs: 4px;
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 16px;
--radius-xl: 24px;
--radius-pill: 999px;

--shadow-1: var(--shadow-1);
--shadow-2: var(--shadow-2);
--shadow-3: var(--shadow-3);
--shadow-ring: var(--ring-focus);

--ease-standard: cubic-bezier(0.2, 0, 0, 1);

--transition-duration-fast: 120ms;
--transition-duration-base: 200ms;
--transition-duration-slow: 320ms;

--animate-slide-in-right: slide-in-right 200ms cubic-bezier(0.2, 0, 0, 1);
--animate-fade-in: fade-in 120ms cubic-bezier(0.2, 0, 0, 1);

@keyframes slide-in-right {
from {
transform: translateX(100%);
}
to {
transform: translateX(0);
}
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
}

@utility phone-actions {
/* A form's main buttons on a phone: the row sticks to the bottom of the screen rather than waiting below the fold,
and leaves room on the right for the floating Ask button. The call site stretches it to its card's edges. */
@media (max-width: 639px) {
position: sticky;
bottom: 0;
z-index: 10;
background: var(--background);
border-top: 1px solid var(--border);
padding-top: 12px;
padding-bottom: 12px;
body:has([data-ask-button]) & {
padding-right: 80px;
}
}
}

@utility tabular {
font-variant-numeric: tabular-nums;
}

@utility label-caps {
font: 700 11px/1 var(--font-sans);
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--text-muted);
}

@utility transcript-text {
font: var(--type-transcript);
}

@utility skeleton {
background: linear-gradient(90deg, var(--surface-neutral) 25%, var(--surface) 50%, var(--surface-neutral) 75%);
background-size: 200% 100%;
animation: skeleton 1.4s ease-in-out infinite;
border-radius: 6px;
}

@layer base {
html {
Expand All @@ -17,9 +167,20 @@
}
*,
::before,
::after {
::after,
::backdrop,
::file-selector-button {
border-color: var(--border);
}
/* What Tailwind 3's preflight did and 4's doesn't: buttons show a hand, placeholders a light grey */
button:not(:disabled),
[role="button"]:not(:disabled) {
cursor: pointer;
}
input::placeholder,
textarea::placeholder {
color: var(--color-gray-400);
}
:focus-visible {
outline: 2px solid var(--aladdin-blue);
outline-offset: 2px;
Expand All @@ -38,50 +199,11 @@
}
}

@layer components {
/* A form's main buttons on a phone: the row sticks to the bottom of the screen rather than waiting below the fold,
and leaves room on the right for the floating Ask button. The call site stretches it to its card's edges. */
@media (max-width: 639px) {
.phone-actions {
position: sticky;
bottom: 0;
z-index: 10;
background: var(--background);
border-top: 1px solid var(--border);
padding-top: 12px;
padding-bottom: 12px;
}
body:has([data-ask-button]) .phone-actions {
padding-right: 80px;
}
}
}

@layer utilities {
.tabular {
font-variant-numeric: tabular-nums;
}
.label-caps {
font: 700 11px/1 var(--font-sans);
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--text-muted);
}
.transcript-text {
font: var(--type-transcript);
}
.skeleton {
background: linear-gradient(90deg, var(--surface-neutral) 25%, var(--surface) 50%, var(--surface-neutral) 75%);
background-size: 200% 100%;
animation: skeleton 1.4s ease-in-out infinite;
border-radius: 6px;
@keyframes skeleton {
from {
background-position: 200% 0;
}
@keyframes skeleton {
from {
background-position: 200% 0;
}
to {
background-position: -200% 0;
}
to {
background-position: -200% 0;
}
}
2 changes: 1 addition & 1 deletion nextjs-frontend/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"config": "",
"css": "app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
Expand Down
2 changes: 1 addition & 1 deletion nextjs-frontend/components/access/access-fields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function AccessFields({
label="Access"
size={size}
columns={3}
className="max-sm:!grid-cols-1"
className="max-sm:grid-cols-1!"
value={value.access}
=> onChange({ ...value, access: v as Access })}
disabled={disabled}
Expand Down
4 changes: 2 additions & 2 deletions nextjs-frontend/components/account/s3-keys.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,15 @@ function Copyable({ label, text }: { label: string; text: string }) {
<div className="flex flex-col gap-1.5">
<span className="text-[13px] font-bold text-fg-strong">{label}</span>
<div className="flex items-start gap-2 rounded-[10px] bg-term-bg py-2.5 pl-3.5 pr-2 text-term-fg">
<pre className="min-w-0 flex-1 select-all whitespace-pre-wrap break-all font-mono text-[12.5px] leading-[1.5]">
<pre className="min-w-0 flex-1 select-all whitespace-pre-wrap break-all font-mono text-[12.5px] leading-normal">
{text}
</pre>
<button
type="button"
>
className={cn(
"inline-flex h-8 shrink-0 items-center gap-1.5 rounded-pill px-3 text-[12.5px] font-bold",
copied ? "bg-green text-white" : "text-[var(--term-blue)] hover:bg-white/10",
copied ? "bg-green text-white" : "text-(--term-blue) hover:bg-white/10",
)}
>
{copied ? <Check className="size-3.5" /> : <Copy className="size-3.5" />}
Expand Down
2 changes: 1 addition & 1 deletion nextjs-frontend/components/account/tokens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ function ShownOnceDialog({ created, onClose }: { created: Created | null; onClos
>
className={cn(
"inline-flex h-8 shrink-0 items-center gap-1.5 rounded-pill px-3 text-[12.5px] font-bold",
copied ? "bg-green text-white" : "text-[var(--term-blue)] hover:bg-white/10",
copied ? "bg-green text-white" : "text-(--term-blue) hover:bg-white/10",
)}
>
{copied ? <Check className="size-3.5" /> : <Copy className="size-3.5" />}
Expand Down
4 changes: 2 additions & 2 deletions nextjs-frontend/components/activity/job-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ function Box({
</span>
<div className="flex min-w-0 flex-col gap-1 text-[13.5px] leading-normal text-fg-strong">
<b className="font-bold text-fg">{title}</b>
{children && <span className="break-words">{children}</span>}
{children && <span className="wrap-break-word">{children}</span>}
</div>
</div>
);
Expand All @@ -140,7 +140,7 @@ function Dl({ rows }: { rows: [string, ReactNode][] }) {
{rows.map(([k, v]) => (
<div key={k} className="contents">
<dt className="text-fg-muted">{k}</dt>
<dd className="min-w-0 break-words text-fg">{v}</dd>
<dd className="min-w-0 wrap-break-word text-fg">{v}</dd>
</div>
))}
</dl>
Expand Down
12 changes: 6 additions & 6 deletions nextjs-frontend/components/activity/log-viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import { cn } from "@/lib/utils";

const TONE: Record<LogTone, string> = {
out: "text-term-muted",
info: "text-[var(--term-blue)]",
ok: "text-[var(--term-green)]",
fail: "text-[var(--term-red)]",
gate: "text-[var(--term-gold)]",
info: "text-(--term-blue)",
ok: "text-(--term-green)",
fail: "text-(--term-red)",
gate: "text-(--term-gold)",
};

/** Download lines as a text file (the full log the server keeps). */
Expand Down Expand Up @@ -97,10 +97,10 @@ export function LogViewer({
const el = e.currentTarget;
follow.current = el.scrollHeight - el.scrollTop - el.clientHeight < 24;
}}
className="max-h-[420px] min-h-[120px] overflow-auto px-4 py-3 focus-visible:outline-offset-[-2px]"
className="max-h-[420px] min-h-[120px] overflow-auto px-4 py-3 focus-visible:-outline-offset-2"
>
{lines.length ? (
<pre className="m-0 whitespace-pre-wrap break-words font-mono">
<pre className="m-0 whitespace-pre-wrap wrap-break-word font-mono">
{lines.map((l, i) => (
<div key={i} className={TONE[i === 0 && l.tone === "out" ? "info" : l.tone]}>
{l.time && <span className="text-term-muted">{l.time} </span>}
Expand Down
2 changes: 1 addition & 1 deletion nextjs-frontend/components/admin/audit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ export function AuditPage() {
)}
</dd>
<dt className="font-bold">Details</dt>
<dd className="break-words">{detail || "No details recorded"}</dd>
<dd className="wrap-break-word">{detail || "No details recorded"}</dd>
<dt className="font-bold">When</dt>
<dd className="tabular">{absolute(e.at)}</dd>
<dt className="font-bold">Who</dt>
Expand Down
2 changes: 1 addition & 1 deletion nextjs-frontend/components/admin/namespaces.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ export function NamespaceDetail({ ns }: { ns: string }) {
})
}
className={cn(
"h-8 w-full appearance-none rounded-sm border border-border pl-2.5 pr-7 text-[13px] font-medium outline-none focus:border-blue",
"h-8 w-full appearance-none rounded-sm border border-border pl-2.5 pr-7 text-[13px] font-medium outline-hidden focus:border-blue",
self ? "bg-surface text-fg-muted" : "bg-background text-fg",
)}
>
567B Expand Down
Loading
Loading
0