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
364 changes: 268 additions & 96 deletions app/globals.css

Large diffs are not rendered by default.

19 changes: 3 additions & 16 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,11 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import { ThemeProvider } from "@/components/ThemeProvider";
import { DialogProvider } from "@/components/DialogProvider";

const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});

const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});

export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
title: "FSR Protokoll Editor",
description: "Sitzungsprotokolle gemeinsam, strukturiert und sicher erfassen.",
};

export default function RootLayout({
Expand All @@ -26,9 +15,7 @@ export default function RootLayout({
}>) {
return (
<html lang="en" suppressHydrationWarning>
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<body>
<ThemeProvider
attribute="class"
defaultTheme="system"
Expand Down
40 changes: 17 additions & 23 deletions components/CommandArgumentForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ import type {
ScalarCommandArgumentSchema,
} from "@/common/sessionCommands";
import { CommandArgumentListInput } from "@/components/CommandArgumentListInput";
import { Badge, Button, FieldLabel } from "@/components/ui/Primitives";

type Props = {
command: RegisteredSessionCommand;
onSubmit: (args: CommandArguments) => void;
onCancel: () => void;
};

const fieldClassName =
"w-full rounded-md border border-slate-200 bg-white px-3 py-2 text-sm text-slate-900 outline-none focus:border-indigo-400 focus:ring-2 focus:ring-indigo-100 dark:border-border dark:bg-zinc-950 dark:text-foreground dark:placeholder:text-muted-foreground dark:focus:border-primary dark:focus:ring-indigo-950";
const fieldClassName = "ui-input text-sm";

export function CommandArgumentForm({ command, onSubmit, onCancel }: Props) {
const initialState = useMemo(() => createInitialFormState(command), [command]);
Expand Down Expand Up @@ -53,12 +53,10 @@ export function CommandArgumentForm({ command, onSubmit, onCancel }: Props) {
const firstFocusableArgumentName = command.arguments[0]?.name ?? null;

return (
<form onSubmit={submit} className="space-y-4">
<div>
<p className="text-sm font-medium text-slate-900 dark:text-slate-100">
/{command.name}
</p>
<p className="text-sm text-slate-500 dark:text-muted-foreground">
<form onSubmit={submit} className="space-y-5">
<div className="rounded-xl bg-accent p-4">
<Badge tone="accent">/{command.name}</Badge>
<p className="mt-2 text-sm leading-relaxed text-accent-foreground">
{command.description}
</p>
</div>
Expand All @@ -70,11 +68,9 @@ export function CommandArgumentForm({ command, onSubmit, onCancel }: Props) {

return (
<div key={argument.name} className="block space-y-1.5">
<span className="text-sm font-medium text-slate-700 dark:text-slate-200">
{argument.label}
</span>
<FieldLabel>{argument.label}</FieldLabel>
{argument.description && (
<span className="block text-xs text-slate-500 dark:text-muted-foreground">
<span className="block text-xs leading-relaxed text-muted-foreground">
{argument.description}
</span>
)}
Expand Down Expand Up @@ -131,19 +127,19 @@ export function CommandArgumentForm({ command, onSubmit, onCancel }: Props) {
</div>

<div className="flex justify-end gap-2 pt-2">
<button
<Button
type="button"
onClick={onCancel}
className="rounded-md px-3 py-2 text-sm font-medium text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-zinc-800"
variant="quiet"
>
Abbrechen
</button>
<button
</Button>
<Button
type="submit"
className="rounded-md bg-indigo-600 px-3 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:bg-primary dark:text-primary-foreground dark:hover:bg-indigo-400"
variant="primary"
>
Einfügen
</button>
</Button>
</div>
</form>
);
Expand All @@ -164,11 +160,9 @@ function ScalarField({
}) {
return (
<label className="block space-y-1.5">
<span className="text-sm font-medium text-slate-700 dark:text-slate-200">
{schema.label}
</span>
<FieldLabel className="mb-0">{schema.label}</FieldLabel>
{schema.description && (
<span className="block text-xs text-slate-500 dark:text-muted-foreground">
<span className="block text-xs leading-relaxed text-muted-foreground">
{schema.description}
</span>
)}
Expand All @@ -195,7 +189,7 @@ function ScalarField({
className={fieldClassName}
/>
)}
{error && <span className="text-xs text-red-500">{error}</span>}
{error && <span className="text-xs font-medium text-destructive" role="alert">{error}</span>}
</label>
);
}
32 changes: 17 additions & 15 deletions components/CommandArgumentListInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import {
listErrorKey,
type CommandArgumentFormErrors,
} from "@/common/commandArguments";
import { Button, FieldLabel } from "@/components/ui/Primitives";

const fieldClassName =
"w-full rounded-md border border-slate-200 bg-white px-3 py-2 text-sm text-slate-900 outline-none focus:border-indigo-400 focus:ring-2 focus:ring-indigo-100 dark:border-border dark:bg-zinc-950 dark:text-foreground dark:placeholder:text-muted-foreground dark:focus:border-primary dark:focus:ring-indigo-950";
const fieldClassName = "ui-input text-sm";

type Props = {
schema: ListCommandArgumentSchema;
Expand Down Expand Up @@ -62,27 +62,29 @@ export function CommandArgumentListInput({
return (
<div className="space-y-3">
{errors[schema.name] && (
<span className="text-xs text-red-500">{errors[schema.name]}</span>
<span className="text-xs font-medium text-destructive" role="alert">{errors[schema.name]}</span>
)}

{items.map((item, itemIndex) => (
<div
key={itemIndex}
className="rounded-md border border-slate-200 p-3 dark:border-border"
className="rounded-xl border border-border bg-[color:var(--muted)]/35 p-3"
>
<div className="mb-2 flex items-center justify-between gap-2">
<span className="text-xs font-medium uppercase tracking-wide text-slate-500 dark:text-muted-foreground">
<span className="text-[0.6875rem] font-bold uppercase tracking-[0.1em] text-muted-foreground">
{itemLabel} {itemIndex + 1}
</span>
{canRemove && (
<button
<Button
type="button"
onClick={() => removeItem(itemIndex)}
className="text-slate-400 hover:text-red-500 dark:text-slate-500 dark:hover:text-red-400"
variant="quiet"
size="icon"
className="h-8 min-h-8 w-8 text-muted-foreground hover:bg-[var(--destructive-soft)] hover:text-destructive"
aria-label={`${itemLabel} entfernen`}
>
<Trash2 size={14} />
</button>
</Button>
)}
</div>

Expand All @@ -106,14 +108,16 @@ export function CommandArgumentListInput({
))}

{canAdd && (
<button
<Button
type="button"
onClick={addItem}
className="flex items-center gap-1 text-sm text-indigo-600 hover:text-indigo-700 dark:text-indigo-400 dark:hover:text-indigo-300"
variant="quiet"
size="sm"
className="text-primary"
>
<Plus size={14} />
{addLabel}
</button>
</Button>
)}
</div>
);
Expand All @@ -134,9 +138,7 @@ function ListField({
}) {
return (
<label className="block space-y-1">
<span className="text-xs font-medium text-slate-600 dark:text-slate-300">
{field.label}
</span>
<FieldLabel className="mb-0 text-xs">{field.label}</FieldLabel>
<input
ref={inputRef}
type={getListFieldInputType(field)}
Expand All @@ -145,7 +147,7 @@ function ListField({
placeholder={field.placeholder}
className={fieldClassName}
/>
{error && <span className="text-xs text-red-500">{error}</span>}
{error && <span className="text-xs font-medium text-destructive" role="alert">{error}</span>}
</label>
);
}
23 changes: 15 additions & 8 deletions components/CommandSuggestions.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from "react";
import { createPortal } from "react-dom";
import { Terminal } from "lucide-react";
import type { RegisteredSessionCommand } from "@/common/sessionCommands";

Expand All @@ -25,16 +26,16 @@ export function CommandSuggestions({
}: Props) {
if (!isOpen || commands.length === 0) return null;

return (
const palette = (
<div
ref={containerRef}
style={{ position, ...style }}
className={
className ??
"bg-white dark:bg-zinc-900 border border-slate-200 dark:border-border rounded-lg shadow-lg z-20"
"glass-popover materialize z-20 rounded-xl p-1"
}
>
<div className="py-1">
<div>
{commands.map((command, index) => {
const isActive = index === activeIndex;
return (
Expand All @@ -45,18 +46,18 @@ export function CommandSuggestions({
event.preventDefault();
onPick(command);
}}
className={`w-full px-3 py-2 text-left flex items-start gap-3 transition-colors ${
className={`focus-ring flex min-h-12 w-full items-start gap-3 rounded-lg px-3 py-2 text-left transition-colors ${
isActive
? "bg-indigo-50 dark:bg-indigo-950/40 text-indigo-700 dark:text-indigo-200"
: "text-slate-700 dark:text-slate-200 hover:bg-slate-50 dark:hover:bg-zinc-800"
? "bg-accent text-accent-foreground"
: "text-secondary-foreground hover:bg-muted"
}`}
>
<span className="mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-slate-100 text-slate-500 dark:bg-zinc-800 dark:text-zinc-300">
<span className="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-muted text-muted-foreground">
<Terminal size={14} />
</span>
<span className="min-w-0 flex-1">
<span className="block text-sm font-medium">/{command.name}</span>
<span className="block text-xs text-slate-500 dark:text-muted-foreground">
<span className="block text-xs text-muted-foreground">
{command.description}
</span>
</span>
Expand All @@ -66,4 +67,10 @@ export function CommandSuggestions({
</div>
</div>
);

if (position === "fixed" && typeof document !== "undefined") {
return createPortal(palette, document.body);
}

return palette;
}
21 changes: 9 additions & 12 deletions components/DialogProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import React, { createContext, useContext, useState, useCallback } from "react";
import { Modal } from "@/components/Modal";
import { Button } from "@/components/ui/Primitives";

type DialogType = "alert" | "confirm" | "prompt";

Expand Down Expand Up @@ -143,15 +144,15 @@ export const DialogProvider = ({ children }: { children: React.ReactNode }) => {
width="sm"
>
<div className="space-y-4">
<p className="text-slate-600 dark:text-slate-300">
<p className="text-sm leading-relaxed text-secondary-foreground">
{dialog.message}
</p>

{dialog.type === "prompt" && (
<input
type={(dialog.options as DialogOptionsPrompt).hiddenInput ? "password" : "text"}
autoFocus
className="w-full px-3 py-2 border border-slate-300 dark:border-slate-700 rounded-md bg-white dark:bg-slate-950 text-slate-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500"
className="ui-input"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyDown={handleKeyDown}
Expand All @@ -161,23 +162,19 @@ export const DialogProvider = ({ children }: { children: React.ReactNode }) => {

<div className="flex justify-end gap-2 pt-2">
{dialog.type !== "alert" && (
<button
<Button
onClick={handleClose}
className="px-4 py-2 text-sm font-medium text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-md transition-colors"
variant="quiet"
>
{dialog.options.cancelText}
</button>
</Button>
)}
<button
<Button
onClick={handleConfirm}
className={`px-4 py-2 text-sm font-medium text-white rounded-md shadow-sm transition-colors ${
dialog.options.destructive
? "bg-red-600 hover:bg-red-700 dark:bg-red-600 dark:hover:bg-red-700"
: "bg-indigo-600 hover:bg-indigo-700 dark:bg-indigo-600 dark:hover:bg-indigo-500"
}`}
variant={dialog.options.destructive ? "destructive" : "primary"}
>
{dialog.options.confirmText}
</button>
</Button>
</div>
</div>
</Modal>
Expand Down
Loading
Loading