diff --git a/resources/views/components/app/context-nav-item.blade.php b/resources/views/components/app/context-nav-item.blade.php new file mode 100644 index 0000000..12abe83 --- /dev/null +++ b/resources/views/components/app/context-nav-item.blade.php @@ -0,0 +1,20 @@ +@props([ + 'href', + 'label', + 'icon', + 'current' => false, +]) + + $current, + 'text-muted hover:bg-main hover:text-copy' => ! $current, + ]) +> + + + + {{ $label }} + diff --git a/resources/views/components/app/toolbar-item.blade.php b/resources/views/components/app/toolbar-item.blade.php new file mode 100644 index 0000000..1fc1f1e --- /dev/null +++ b/resources/views/components/app/toolbar-item.blade.php @@ -0,0 +1,20 @@ +@props([ + 'href', + 'label', + 'icon', + 'current' => false, +]) + + $current, + 'text-toolbar-muted hover:bg-white/8 hover:text-white' => ! $current, + ]) +> + + + + {{ $label }} + diff --git a/resources/views/components/layouts/app-shell.blade.php b/resources/views/components/layouts/app-shell.blade.php index 304e1c6..7d39009 100644 --- a/resources/views/components/layouts/app-shell.blade.php +++ b/resources/views/components/layouts/app-shell.blade.php @@ -83,19 +83,12 @@ class="flex size-10 items-center justify-center rounded-lg bg-white/10 text-whit @@ -106,11 +99,11 @@ class="flex size-10 items-center justify-center rounded-lg bg-white/10 text-whit class="bg-panel px-5 py-5 lg:w-72 lg:px-6 lg:py-8" >
- +
-

Current app

+

Current app

Ideas

@@ -118,19 +111,12 @@ class="bg-panel px-5 py-5 lg:w-72 lg:px-6 lg:py-8" diff --git a/resources/views/components/ui/avatar.blade.php b/resources/views/components/ui/avatar.blade.php new file mode 100644 index 0000000..a2ccc77 --- /dev/null +++ b/resources/views/components/ui/avatar.blade.php @@ -0,0 +1,17 @@ +@props([ + 'name' => null, + 'initials' => null, + 'size' => 'md', +]) + +@php + $sizeClasses = [ + 'sm' => 'size-8 text-xs', + 'md' => 'size-10 text-sm', + 'lg' => 'size-12 text-base', + ]; +@endphp + +class(['inline-flex items-center justify-center rounded-md bg-accent-soft font-medium text-accent-strong', $sizeClasses[$size] ?? $sizeClasses['md']]) }}> + {{ $initials ?? \Illuminate\Support\Str::of((string) $name)->trim()->explode(' ')->filter()->take(2)->map(fn (string $part): string => mb_strtoupper(mb_substr($part, 0, 1)))->implode('') ?: '?' }} + diff --git a/resources/views/components/ui/badge.blade.php b/resources/views/components/ui/badge.blade.php new file mode 100644 index 0000000..0f51ef1 --- /dev/null +++ b/resources/views/components/ui/badge.blade.php @@ -0,0 +1,15 @@ +@props([ + 'tone' => 'neutral', +]) + +@php + $toneClasses = [ + 'neutral' => 'bg-main text-muted', + 'accent' => 'bg-accent-soft text-accent-strong', + 'success' => 'bg-emerald-500/12 text-emerald-700 dark:text-emerald-300', + ]; +@endphp + +class(['inline-flex items-center rounded-md px-2 py-1 text-xs font-medium', $toneClasses[$tone] ?? $toneClasses['neutral']]) }}> + {{ $slot }} + diff --git a/resources/views/components/ui/button.blade.php b/resources/views/components/ui/button.blade.php new file mode 100644 index 0000000..a55b908 --- /dev/null +++ b/resources/views/components/ui/button.blade.php @@ -0,0 +1,21 @@ +@props([ + 'type' => 'button', + 'variant' => 'primary', +]) + +@php + $variantClasses = [ + 'primary' => 'bg-accent text-white hover:bg-accent-strong', + 'secondary' => 'bg-main text-copy hover:bg-panel', + 'ghost' => 'bg-transparent text-copy hover:bg-main', + ]; + + $baseClasses = 'inline-flex items-center justify-center gap-2 rounded-md px-4 py-2 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50'; +@endphp + + diff --git a/resources/views/components/ui/dropdown.blade.php b/resources/views/components/ui/dropdown.blade.php new file mode 100644 index 0000000..2ef70a1 --- /dev/null +++ b/resources/views/components/ui/dropdown.blade.php @@ -0,0 +1,13 @@ +@props([ + 'label' => 'Menu', +]) + +
class(['group relative']) }}> + + {{ $label }} + + +
+ {{ $slot }} +
+
diff --git a/resources/views/components/ui/input.blade.php b/resources/views/components/ui/input.blade.php new file mode 100644 index 0000000..45279c8 --- /dev/null +++ b/resources/views/components/ui/input.blade.php @@ -0,0 +1,20 @@ +@props([ + 'label' => null, + 'error' => null, +]) + +@php + $fieldClasses = 'block w-full rounded-md border-0 bg-main px-3 py-2 text-sm text-copy shadow-none ring-1 ring-inset ring-transparent transition placeholder:text-muted focus:outline-none focus:ring-2 focus:ring-accent'; +@endphp + + diff --git a/resources/views/components/ui/modal.blade.php b/resources/views/components/ui/modal.blade.php new file mode 100644 index 0000000..996920e --- /dev/null +++ b/resources/views/components/ui/modal.blade.php @@ -0,0 +1,13 @@ +@props([ + 'title' => null, +]) + +
class(['rounded-md bg-panel p-5']) }}> + @if ($title) +

{{ $title }}

+ @endif + +
+ {{ $slot }} +
+
diff --git a/resources/views/components/ui/page-header.blade.php b/resources/views/components/ui/page-header.blade.php new file mode 100644 index 0000000..53b75e6 --- /dev/null +++ b/resources/views/components/ui/page-header.blade.php @@ -0,0 +1,26 @@ +@props([ + 'eyebrow' => null, + 'title' => null, + 'description' => null, + 'actions' => null, +]) + +
class(['space-y-3']) }}> + @if ($eyebrow) +

{{ $eyebrow }}

+ @endif + + @if ($title) +

{{ $title }}

+ @endif + + @if ($description) +

{{ $description }}

+ @endif + + @if ($actions) +
+ {{ $actions }} +
+ @endif +
diff --git a/resources/views/components/ui/panel.blade.php b/resources/views/components/ui/panel.blade.php new file mode 100644 index 0000000..5ef8a91 --- /dev/null +++ b/resources/views/components/ui/panel.blade.php @@ -0,0 +1,20 @@ +@props([ + 'title' => null, + 'subtitle' => null, +]) + +
class(['rounded-md bg-panel px-5 py-5']) }}> + @if ($title || $subtitle) +
+ @if ($title) +

{{ $title }}

+ @endif + + @if ($subtitle) +

{{ $subtitle }}

+ @endif +
+ @endif + + {{ $slot }} +
diff --git a/resources/views/components/ui/select.blade.php b/resources/views/components/ui/select.blade.php new file mode 100644 index 0000000..de42686 --- /dev/null +++ b/resources/views/components/ui/select.blade.php @@ -0,0 +1,22 @@ +@props([ + 'label' => null, + 'error' => null, +]) + +@php + $fieldClasses = 'block w-full rounded-md border-0 bg-main px-3 py-2 text-sm text-copy shadow-none ring-1 ring-inset ring-transparent transition focus:outline-none focus:ring-2 focus:ring-accent'; +@endphp + + diff --git a/resources/views/components/ui/textarea.blade.php b/resources/views/components/ui/textarea.blade.php new file mode 100644 index 0000000..eee6879 --- /dev/null +++ b/resources/views/components/ui/textarea.blade.php @@ -0,0 +1,20 @@ +@props([ + 'label' => null, + 'error' => null, +]) + +@php + $fieldClasses = 'block w-full rounded-md border-0 bg-main px-3 py-2 text-sm text-copy shadow-none ring-1 ring-inset ring-transparent transition placeholder:text-muted focus:outline-none focus:ring-2 focus:ring-accent'; +@endphp + + diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index 64df9e8..5c62701 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -65,25 +65,22 @@
-
-

Workspace

-

Turn rough product thoughts into team-ready software proposals.

-

- This shell is the first pass at the shared product frame: app switcher on the left, context navigation beside it, - and a main work area ready for ideas, planning, development, testing, security, ops, and admin tools. -

-
+
-
+

Current draft

Private idea workspace

- + Draft - +
@@ -96,10 +93,12 @@ features can slot in without rewriting the frame.

-
+ -
-

Next in this app

+
  • @@ -115,7 +114,7 @@ Propose a polished draft to the team
-
+
diff --git a/tests/Feature/Ui/UiComponentsTest.php b/tests/Feature/Ui/UiComponentsTest.php new file mode 100644 index 0000000..062efb7 --- /dev/null +++ b/tests/Feature/Ui/UiComponentsTest.php @@ -0,0 +1,37 @@ +Save'); + + expect($rendered)->toContain('type="button"'); + expect($rendered)->toContain('Save'); +}); + +it('renders the shared form controls', function (): void { + $rendered = Blade::render(<<<'BLADE' + + + + + + BLADE); + + expect($rendered)->toContain('Name'); + expect($rendered)->toContain('Summary'); + expect($rendered)->toContain('State'); +}); + +it('renders the shared display primitives', function (): void { + $rendered = Blade::render('Draft'); + + expect($rendered)->toContain('Draft'); + expect($rendered)->toContain('TO'); +}); + +it('renders the shared avatar fallback from a name', function (): void { + $rendered = Blade::render(''); + + expect($rendered)->toContain('TO'); +});