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
655 changes: 655 additions & 0 deletions ui/.claude/skills/nebari-ui/SKILL.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion ui/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/ui",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
Expand Down
12 changes: 6 additions & 6 deletions ui/src/components/app-bits.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { CheckCircle2, CircleDashed, Loader2, PauseCircle, XCircle } from 'lucide-react';
import { type ReactNode, useState } from 'react';
import { Badge } from '@/ui/badge';
import { Button } from '@/ui/button';
import { Card, CardContent } from '@/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import {
Dialog,
DialogClose,
Expand All @@ -11,9 +11,9 @@ import {
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/ui/dialog';
import { Input } from '@/ui/input';
import { Label } from '@/ui/label';
} from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { cn } from '@/lib/utils';

export function PhaseBadge({ phase }: { phase: string }) {
Expand Down
14 changes: 7 additions & 7 deletions ui/src/components/app-form.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Plus, Trash2, UploadCloud } from 'lucide-react';
import { useMemo, useState } from 'react';
import type { App, AppCreate, AppPatch, AppSource, EnvVar } from '@/lib/types';
import { Button } from '@/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/ui/card';
import { Input } from '@/ui/input';
import { Label } from '@/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/ui/select';
import { Switch } from '@/ui/switch';
import { Tabs, TabsList, TabsPanel, TabsTab } from '@/ui/tabs';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Switch } from '@/components/ui/switch';
import { Tabs, TabsList, TabsPanel, TabsTab } from '@/components/ui/tabs';

const SUBDOMAIN_RE = /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/;

Expand Down
File renamed without changes.
8 changes: 4 additions & 4 deletions ui/src/components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,22 @@ import nebariLogoDark from '@/assets/nebari-logo_dark.svg';
import { isThemeMode, type ThemeMode } from '@/hooks/use-theme-preference';
import { getBranding } from '@/lib/branding';
import { cn } from '@/lib/utils';
import { Avatar, AvatarFallback } from '@/ui/avatar';
import { Avatar, AvatarFallback } from '@/components/avatar';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuPortal,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/ui/dropdown-menu';
} from '@/components/ui/dropdown-menu';
import {
MenuBarActions,
MenuBarBrand,
MenuBarNav,
NavigationMenu,
NavLink,
} from '@/ui/navigation-menu';
} from '@/components/ui/navigation-menu';

const NAV = [
{ to: '/', label: 'Dashboard', icon: LayoutDashboard, end: true },
Expand Down Expand Up @@ -78,7 +78,7 @@ export function Header({
const brandedLogo = isDarkMode ? (logoUrlDark ?? logoUrl) : logoUrl;

return (
<NavigationMenu className="h-14 justify-between border-header-border bg-header-background pl-4 text-header-foreground">
<NavigationMenu className="h-14 justify-between border-header-border bg-header pl-4 text-header-foreground">
<MenuBarBrand href="/" aria-label="Go to homepage">
{brandedLogo ? (
<img src={brandedLogo} alt={title || 'Nebari'} className="h-8 w-auto" />
Expand Down
4 changes: 2 additions & 2 deletions ui/src/components/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Header } from '@/components/header';
import { useTheme } from '@/hooks/theme-provider';
import { getUser, logout } from '@/lib/auth';
import { getBranding } from '@/lib/branding';
import { TooltipProvider } from '@/ui/tooltip';
import { TooltipProvider } from '@/components/ui/tooltip';

export function Layout() {
const user = getUser();
Expand All @@ -26,7 +26,7 @@ export function Layout() {
onSignOut={() => void logout()}
/>

<main className="min-w-0 flex-1 bg-body-background px-10 py-8">
<main className="min-w-0 flex-1 bg-canvas px-10 py-8">
<Outlet />
</main>

Expand Down
10 changes: 5 additions & 5 deletions ui/src/components/onboarding.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { BookOpen, Copy, Rocket, Sparkles } from 'lucide-react';
import { type ReactNode, useState } from 'react';
import { Link } from 'react-router-dom';
import { Button } from '@/ui/button';
import { Card, CardContent } from '@/ui/card';
import { toast } from '@/ui/toast';
import { copyText } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { toast } from '@/components/ui/toast';
import { copyText } from '@/lib/clipboard';

/** First-run guidance shown when no apps exist yet. */
export function Onboarding() {
Expand All @@ -17,7 +17,7 @@ export function Onboarding() {
setCopied(true);
setTimeout(() => setCopied(false), 1500);
} catch {
toast.error('Could not copy to clipboard');
toast.add({ type: 'error', title: 'Could not copy to clipboard' });
}
};

Expand Down
File renamed without changes.
File renamed without changes.
4 changes: 2 additions & 2 deletions ui/src/ui/button.tsx → ui/src/components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { useRender } from '@base-ui/react/use-render';
import { cva, type VariantProps } from 'class-variance-authority';
import { Children, isValidElement, type ReactNode } from 'react';
import { cn } from '@/lib/utils';
import { Spinner } from '@/ui/spinner';
import { Spinner } from '@/components/ui/spinner';

const buttonVariants = cva(
"inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium underline-offset-4 outline-none motion-safe:transition-[color,background-color,border-color,opacity,transform] motion-safe:duration-[--duration-fast] motion-safe:ease-[--ease-standard] motion-safe:active:scale-[0.97] hover:underline focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background data-[disabled]:pointer-events-none data-[disabled]:text-muted-foreground data-[disabled]:no-underline data-[disabled]:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium underline-offset-4 outline-none motion-safe:transition-[color,background-color,border-color,opacity,transform] motion-safe:duration-(--duration-fast) motion-safe:ease-(--ease-standard) motion-safe:active:scale-[0.97] hover:underline focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background data-[disabled]:pointer-events-none data-[disabled]:text-muted-foreground data-[disabled]:no-underline data-[disabled]:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
{
variants: {
// Disabled and loading collapse to a muted look (Figma): the component
Expand Down
File renamed without changes.
File renamed without changes.
26 changes: 20 additions & 6 deletions ui/src/ui/code-block.tsx → ui/src/components/ui/code-block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
useContext,
useState,
} from 'react';
import { cn, copyText } from '@/lib/utils';
import { cn } from '@/lib/utils';

interface CodeBlockContextValue {
/** The raw snippet, shared so descendants don't re-thread the text. */
Expand All @@ -25,6 +25,14 @@ interface CodeBlockContextValue {

const CodeBlockContext = createContext<CodeBlockContextValue | null>(null);

/**
* Reads the snippet and layout flags shared by the nearest {@link CodeBlock}.
* Use it to author a custom part — a line counter, a download button, a
* language badge — without re-threading `code` through props. Pass the calling
* component's name so a missing root reports which part failed; the context has
* no default value, so a call outside {@link CodeBlock} throws rather than
* rendering against an empty snippet.
*/
function useCodeBlockContext(component: string): CodeBlockContextValue {
const context = useContext(CodeBlockContext);
if (!context) {
Expand Down Expand Up @@ -230,7 +238,7 @@ function CodeBlockBody({
}

const codeBlockCopyButtonVariants = cva(
'inline-flex shrink-0 items-center justify-center rounded-md text-muted-foreground outline-none motion-safe:transition-[color,background-color,opacity,transform] motion-safe:duration-[--duration-fast] motion-safe:ease-[--ease-standard] motion-safe:active:scale-[0.97] hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 focus-visible:ring-ring [&_svg]:pointer-events-none',
'inline-flex shrink-0 items-center justify-center rounded-md text-muted-foreground outline-none motion-safe:transition-[color,background-color,opacity,transform] motion-safe:duration-(--duration-fast) motion-safe:ease-(--ease-standard) motion-safe:active:scale-[0.97] hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 focus-visible:ring-ring [&_svg]:pointer-events-none',
{
variants: {
size: {
Expand Down Expand Up @@ -288,12 +296,12 @@ function CodeBlockCopyButton({
async (event: React.MouseEvent<HTMLButtonElement>) => {
onClick?.(event);
try {
await copyText(code);
await navigator.clipboard.writeText(code);
setCopied(true);
window.setTimeout(() => setCopied(false), 2000);
} catch {
// A denied permission rejects — keep the button resting rather than
// flashing a false confirmation.
// Insecure context or a denied permission rejects writeText — keep the
// button resting rather than flashing a false confirmation.
setCopied(false);
}
},
Expand Down Expand Up @@ -321,11 +329,17 @@ function CodeBlockCopyButton({
);
}

export type { CodeBlockBodyProps, CodeBlockCopyButtonProps, CodeBlockProps };
export type {
CodeBlockBodyProps,
CodeBlockContextValue,
CodeBlockCopyButtonProps,
CodeBlockProps,
};
export {
CodeBlock,
CodeBlockBody,
CodeBlockCopyButton,
CodeBlockHeader,
codeBlockCopyButtonVariants,
useCodeBlockContext,
};
6 changes: 3 additions & 3 deletions ui/src/ui/dialog.tsx → ui/src/components/ui/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Dialog as DialogPrimitive } from '@base-ui/react/dialog';
import { XIcon } from 'lucide-react';
import type * as React from 'react';
import { cn } from '@/lib/utils';
import { Button } from '@/ui/button';
import { Button } from '@/components/ui/button';

type DialogProps = DialogPrimitive.Root.Props;

Expand Down Expand Up @@ -49,7 +49,7 @@ function DialogOverlay({ className, ...props }: DialogOverlayProps) {
data-slot="dialog-overlay"
className={(state) =>
cn(
'fixed inset-0 z-50 bg-scrim data-[starting-style]:opacity-0 data-[ending-style]:opacity-0 motion-safe:transition-[opacity] motion-safe:duration-[--duration-base] motion-safe:ease-[--ease-standard]',
'fixed inset-0 z-50 bg-scrim data-[starting-style]:opacity-0 data-[ending-style]:opacity-0 motion-safe:transition-[opacity] motion-safe:duration-(--duration-base) motion-safe:ease-(--ease-standard)',
typeof className === 'function' ? className(state) : className,
)
}
Expand Down Expand Up @@ -85,7 +85,7 @@ function DialogContent({
data-slot="dialog-content"
className={(state) =>
cn(
'relative grid max-h-[min(calc(100vh-2rem),42rem)] w-full max-w-lg gap-4 overflow-hidden rounded-md border border-border bg-popover p-6 text-popover-foreground shadow-lg outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background data-[starting-style]:translate-y-2 data-[starting-style]:scale-95 data-[starting-style]:opacity-0 data-[ending-style]:translate-y-2 data-[ending-style]:scale-95 data-[ending-style]:opacity-0 motion-safe:transition-[opacity,transform] motion-safe:duration-[--duration-base] motion-safe:ease-[--ease-emphasized]',
'relative grid max-h-[min(calc(100vh-2rem),42rem)] w-full max-w-lg gap-4 overflow-hidden rounded-md border border-border bg-popover p-6 text-popover-foreground shadow-lg outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background data-[starting-style]:translate-y-2 data-[starting-style]:scale-95 data-[starting-style]:opacity-0 data-[ending-style]:translate-y-2 data-[ending-style]:scale-95 data-[ending-style]:opacity-0 motion-safe:transition-[opacity,transform] motion-safe:duration-(--duration-base) motion-safe:ease-(--ease-emphasized)',
typeof className === 'function' ? className(state) : className,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { cva, type VariantProps } from 'class-variance-authority';
import { CheckIcon, ChevronRightIcon, ChevronsUpDownIcon } from 'lucide-react';
import type * as React from 'react';
import { cn } from '@/lib/utils';
import { Button, type ButtonProps } from '@/ui/button';
import { Button, type ButtonProps } from '@/components/ui/button';

const dropdownMenuItemVariants = cva(
'relative flex w-full cursor-default items-center gap-2 rounded-[calc(var(--radius-md)-var(--spacing))] px-1.5 py-1 text-sm outline-hidden select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring motion-safe:transition-[color,background-color] motion-safe:duration-[--duration-fast] motion-safe:ease-[--ease-standard]',
'relative flex w-full cursor-default items-center gap-2 rounded-[calc(var(--radius-md)-var(--spacing))] px-1.5 py-1 text-sm outline-hidden select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring motion-safe:transition-[color,background-color] motion-safe:duration-(--duration-fast) motion-safe:ease-(--ease-standard)',
{
variants: {
variant: {
Expand Down Expand Up @@ -155,7 +155,7 @@ function DropdownMenuContent({
<MenuPrimitive.Popup
data-slot="dropdown-menu-content"
className={cn(
'z-50 min-w-60 origin-(--transform-origin) rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 data-[starting-style]:translate-y-1 data-[starting-style]:opacity-0 data-[ending-style]:translate-y-1 data-[ending-style]:opacity-0 motion-safe:transition-[opacity,transform] motion-safe:duration-[--duration-base] motion-safe:ease-[--ease-emphasized]',
'z-50 min-w-60 origin-(--transform-origin) rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 data-[starting-style]:translate-y-1 data-[starting-style]:opacity-0 data-[ending-style]:translate-y-1 data-[ending-style]:opacity-0 motion-safe:transition-[opacity,transform] motion-safe:duration-(--duration-base) motion-safe:ease-(--ease-emphasized)',
className,
)}
{...props}
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion ui/src/ui/input.tsx → ui/src/components/ui/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function Input({ className, ...props }: InputProps) {
<InputPrimitive
data-slot="input"
className={cn(
'peer flex w-full rounded-md border border-input bg-background px-3 py-1.5 text-foreground text-sm outline-none placeholder:text-muted-foreground motion-safe:transition-[color,background-color,border-color,box-shadow] motion-safe:duration-[--duration-fast] motion-safe:ease-[--ease-standard] hover:border-border-strong focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-50 aria-invalid:border-destructive-foreground aria-invalid:pr-9 aria-invalid:ring-2 aria-invalid:ring-destructive-foreground aria-invalid:focus-visible:ring-destructive-foreground data-[invalid]:border-destructive-foreground data-[invalid]:pr-9 data-[invalid]:ring-2 data-[invalid]:ring-destructive-foreground',
'peer flex w-full rounded-md border border-input bg-background px-3 py-1.5 text-foreground text-sm outline-none placeholder:text-muted-foreground motion-safe:transition-[color,background-color,border-color,box-shadow] motion-safe:duration-(--duration-fast) motion-safe:ease-(--ease-standard) hover:border-border-strong focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-50 aria-invalid:border-destructive-foreground aria-invalid:pr-9 aria-invalid:ring-2 aria-invalid:ring-destructive-foreground aria-invalid:focus-visible:ring-destructive-foreground data-[invalid]:border-destructive-foreground data-[invalid]:pr-9 data-[invalid]:ring-2 data-[invalid]:ring-destructive-foreground',
className,
)}
{...props}
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
type DropdownMenuProps,
DropdownMenuTrigger,
type DropdownMenuTriggerProps,
} from '@/ui/dropdown-menu';
} from '@/components/ui/dropdown-menu';

type NavLinkProps = useRender.ComponentProps<'a'> & {
/** Marks the link as the current page or section. */
Expand Down Expand Up @@ -54,7 +54,7 @@ type MenuBarActionsProps = ComponentProps<'div'>;
* Shared styles for application-level navigation links and dropdown triggers.
*/
const navLinkVariants = cva(
'relative inline-flex h-10 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap rounded-md px-2.5 font-medium text-foreground text-sm underline-offset-4 outline-none hover:bg-muted hover:underline focus-visible:ring-2 focus-visible:ring-ring data-[active=true]:after:absolute data-[active=true]:after:right-2 data-[active=true]:after:bottom-0 data-[active=true]:after:left-2 data-[active=true]:after:h-0.5 data-[active=true]:after:bg-primary data-[active=true]:after:content-[""] data-[disabled]:pointer-events-none data-[disabled]:bg-muted/50 data-[disabled]:text-muted-foreground data-[disabled]:no-underline motion-safe:transition-[color,background-color,border-color,opacity,transform] motion-safe:duration-[--duration-fast] motion-safe:ease-[--ease-standard] motion-safe:active:scale-[0.97] [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*="size-"])]:size-4',
'relative inline-flex h-10 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap rounded-md px-2.5 font-medium text-foreground text-sm underline-offset-4 outline-none hover:bg-muted hover:underline focus-visible:ring-2 focus-visible:ring-ring data-[active=true]:after:absolute data-[active=true]:after:right-2 data-[active=true]:after:bottom-0 data-[active=true]:after:left-2 data-[active=true]:after:h-0.5 data-[active=true]:after:bg-primary data-[active=true]:after:content-[""] data-[disabled]:pointer-events-none data-[disabled]:bg-muted/50 data-[disabled]:text-muted-foreground-strong data-[disabled]:no-underline motion-safe:transition-[color,background-color,border-color,opacity,transform] motion-safe:duration-(--duration-fast) motion-safe:ease-(--ease-standard) motion-safe:active:scale-[0.97] [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*="size-"])]:size-4',
);

/**
Expand Down
File renamed without changes.
Loading
Loading