🎨 Add new Flux icons: implement multiple reusable icon components (e.g., hand-raised, hand-thumb-up, heart, hashtag, home) with variant support for improved UI consistency.

This commit is contained in:
HolgerHatGarKeineNode
2026-01-23 23:00:02 +01:00
parent 578e4f13fc
commit b30fec150c
792 changed files with 307541 additions and 117 deletions

1
.gitignore vendored
View File

@@ -23,4 +23,3 @@ yarn-error.log
/.sisyphus /.sisyphus
/.opencode /.opencode
.switch-omo-config* .switch-omo-config*
/videos

2075
design.pen Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -65,16 +65,6 @@ services:
- ping - ping
retries: 3 retries: 3
timeout: 5s timeout: 5s
relay:
ports:
- "7000:7000"
volumes:
- ./relay:/usr/src/app/db
- ./relay/config.toml:/usr/src/app/config.toml
image: scsibug/nostr-rs-relay:latest
user: "${DOCKER_UID:-1000}:${DOCKER_GID:-1000}"
networks:
- sail
networks: networks:
sail: sail:
driver: bridge driver: bridge

View File

@@ -20,36 +20,79 @@
@custom-variant dark (&:where(.dark, .dark *)); @custom-variant dark (&:where(.dark, .dark *));
/* ---------------------------------------- /* ----------------------------------------
Theme tokens Theme tokens (Einundzwanzig Design System)
----------------------------------------- */ ----------------------------------------- */
@theme { @theme {
--font-sans: "Inconsolata", monospace; --font-sans: "Inconsolata", monospace;
--color-zinc-50: var(--color-neutral-50); /* Background colors */
--color-zinc-100: var(--color-neutral-100); --color-bg-page: #0A0A0B;
--color-zinc-200: var(--color-neutral-200); --color-bg-surface: #111113;
--color-zinc-300: var(--color-neutral-300); --color-bg-elevated: #1A1A1D;
--color-zinc-400: var(--color-neutral-400);
--color-zinc-500: var(--color-neutral-500);
--color-zinc-600: var(--color-neutral-600);
--color-zinc-700: var(--color-neutral-700);
--color-zinc-800: var(--color-neutral-800);
--color-zinc-900: var(--color-neutral-900);
--color-zinc-950: var(--color-neutral-950);
--color-accent: var(--color-neutral-800); /* Border colors */
--color-accent-content: var(--color-neutral-800); --color-border-default: #2A2A2E;
--color-accent-foreground: var(--color-white); --color-border-subtle: #1F1F23;
/* Text colors */
--color-text-primary: #FFFFFF;
--color-text-secondary: #ADADB0;
--color-text-tertiary: #8B8B90;
--color-text-muted: #FFFFFFCC;
--color-text-disabled: #6B6B70;
/* Brand colors */
--color-orange-primary: #FF5C00;
--color-orange-light: #FF8A4C;
--color-green-success: #22C55E;
--color-purple-accent: #7c3aed;
--color-green-nostr: #4a7c59;
/* Zinc aliases for compatibility */
--color-zinc-50: #fafafa;
--color-zinc-100: #f4f4f5;
--color-zinc-200: #e4e4e7;
--color-zinc-300: #d4d4d8;
--color-zinc-400: #a1a1aa;
--color-zinc-500: #71717a;
--color-zinc-600: #52525b;
--color-zinc-700: #3f3f46;
--color-zinc-800: #27272a;
--color-zinc-900: #18181b;
--color-zinc-950: #09090b;
/* Accent colors (orange brand) */
--color-accent: var(--color-orange-primary);
--color-accent-content: var(--color-orange-primary);
--color-accent-foreground: var(--color-text-primary);
} }
/* ---------------------------------------- /* ----------------------------------------
Dark mode overrides Dark mode overrides (Dark is default for Einundzwanzig)
----------------------------------------- */ ----------------------------------------- */
@layer theme { @layer theme {
.dark { .dark,
--color-accent: var(--color-white); :root {
--color-accent-content: var(--color-white); --color-accent: var(--color-orange-primary);
--color-accent-foreground: var(--color-neutral-800); --color-accent-content: var(--color-orange-primary);
--color-accent-foreground: var(--color-text-primary);
/* Flux UI color overrides */
--white: var(--color-text-primary);
--black: var(--color-bg-page);
/* Zinc scale for dark mode */
--zinc-50: var(--color-bg-elevated);
--zinc-100: var(--color-bg-surface);
--zinc-200: var(--color-border-default);
--zinc-300: var(--color-border-subtle);
--zinc-400: var(--color-text-disabled);
--zinc-500: var(--color-text-tertiary);
--zinc-600: var(--color-text-secondary);
--zinc-700: var(--color-text-secondary);
--zinc-800: var(--color-text-primary);
--zinc-900: var(--color-text-primary);
--zinc-950: var(--color-text-primary);
} }
} }
@@ -68,6 +111,17 @@
button { button {
@apply cursor-pointer; @apply cursor-pointer;
} }
/* Ensure body has grid layout for Flux components */
body:has(>[data-flux-main]) {
display: grid;
grid-template-rows: auto 1fr auto;
grid-template-columns: min-content minmax(0, 1fr) min-content;
grid-template-areas:
"header header header"
"sidebar main aside"
"sidebar footer aside";
}
} }
/* ---------------------------------------- /* ----------------------------------------
@@ -115,3 +169,188 @@ select:focus[data-flux-control] {
.leaflet-container a { .leaflet-container a {
color: unset !important; color: unset !important;
} }
/* ----------------------------------------
Einundzwanzig Design System - Flux Overrides
----------------------------------------- */
@layer components {
/* Page backgrounds */
body {
@apply bg-bg-page text-text-primary;
}
/* Cards and surfaces */
[data-flux-card] {
@apply bg-bg-surface border-border-subtle;
}
/* Primary buttons (orange) */
[data-flux-button][data-variant="primary"] {
@apply bg-orange-primary text-text-primary hover:bg-orange-light;
}
/* Default/ghost buttons */
[data-flux-button]:not([data-variant]) {
@apply border-border-default text-text-secondary hover:text-text-primary hover:border-border-default;
}
/* Input fields */
[data-flux-input],
[data-flux-control] {
@apply bg-bg-surface border-border-default text-text-primary placeholder:text-text-disabled;
}
[data-flux-input]:focus,
[data-flux-control]:focus {
@apply border-orange-primary ring-orange-primary/20;
}
/* Navigation */
[data-flux-navbar] {
@apply bg-bg-surface border-b border-border-default;
}
[data-flux-navbar-item][data-current="true"] {
@apply border-b-2 border-orange-primary text-text-primary;
}
/* Modals */
[data-flux-modal] {
@apply bg-bg-surface border-border-subtle;
}
/* Dropdowns/Menus */
[data-flux-menu] {
@apply bg-bg-elevated border-border-default;
}
[data-flux-menu-item]:hover {
@apply bg-bg-surface;
}
/* Tables */
[data-flux-table] {
@apply bg-bg-surface;
}
[data-flux-table] th {
@apply bg-bg-elevated text-text-secondary border-border-default;
}
[data-flux-table] td {
@apply border-border-subtle text-text-primary;
}
/* Badges */
[data-flux-badge] {
@apply bg-bg-elevated text-text-secondary border-border-default;
}
[data-flux-badge][data-color="green"] {
@apply bg-green-success/20 text-green-success border-green-success/30;
}
[data-flux-badge][data-color="orange"] {
@apply bg-orange-primary/20 text-orange-primary border-orange-primary/30;
}
/* Tabs */
[data-flux-tab][data-selected="true"] {
@apply border-orange-primary text-text-primary;
}
/* Switch */
[data-flux-switch][data-checked="true"] {
@apply bg-orange-primary;
}
/* Checkbox */
[data-flux-checkbox]:checked {
@apply bg-orange-primary border-orange-primary;
}
/* Radio */
[data-flux-radio]:checked {
@apply border-orange-primary;
}
[data-flux-radio]:checked::before {
@apply bg-orange-primary;
}
/* Separators */
[data-flux-separator] {
@apply bg-border-default;
}
/* Callouts - Info boxes */
.callout-warning {
@apply bg-gradient-to-b from-amber-500/20 to-transparent border border-amber-500;
}
.callout-success {
@apply bg-gradient-to-b from-green-nostr/20 to-transparent border border-green-nostr;
}
.callout-purple {
@apply bg-gradient-to-b from-purple-accent/20 to-transparent border border-purple-accent;
}
}
/* ----------------------------------------
Einundzwanzig Custom Components
----------------------------------------- */
@layer components {
/* Feature cards with accent borders */
.feature-card {
@apply bg-bg-surface rounded-xl p-5 border border-border-subtle;
}
.feature-card-nostr {
@apply bg-bg-surface rounded-xl p-5 border border-green-nostr;
}
.feature-card-lightning {
@apply bg-bg-surface rounded-xl p-5 border border-purple-accent;
}
/* Info boxes with gradients */
.info-box {
@apply rounded-xl p-4 border;
}
.info-box-warning {
@apply rounded-xl p-4 border border-amber-500 bg-gradient-to-b from-amber-500/20 to-transparent;
}
.info-box-success {
@apply rounded-xl p-4 border border-green-nostr bg-gradient-to-b from-green-nostr/20 to-transparent;
}
/* Header navigation */
.nav-item {
@apply px-4 py-3 text-text-secondary hover:text-text-primary transition-colors;
}
.nav-item-active {
@apply px-4 py-3 text-text-primary hover:text-text-primary transition-colors border-b-2 border-orange-primary;
}
/* Status badges */
.status-badge {
@apply inline-flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm;
}
.status-badge-active {
@apply inline-flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm bg-green-success/20 text-green-success;
}
.status-badge-pending {
@apply inline-flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm bg-amber-500/20 text-amber-500;
}
/* Brand icon container */
.brand-icon {
@apply w-8 h-8 rounded-lg bg-orange-primary flex items-center justify-center;
}
}

View File

@@ -0,0 +1,38 @@
@blaze
@props([
'color' => null,
])
@php
$classes = Flux::classes()
->add(match ($color) {
'slate' => '[--color-accent:var(--color-slate-800)] [--color-accent-content:var(--color-slate-800)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-white)] dark:[--color-accent-content:var(--color-white)] dark:[--color-accent-foreground:var(--color-slate-800)]',
'gray' => '[--color-accent:var(--color-gray-800)] [--color-accent-content:var(--color-gray-800)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-white)] dark:[--color-accent-content:var(--color-white)] dark:[--color-accent-foreground:var(--color-gray-800)]',
'zinc' => '[--color-accent:var(--color-zinc-800)] [--color-accent-content:var(--color-zinc-800)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-white)] dark:[--color-accent-content:var(--color-white)] dark:[--color-accent-foreground:var(--color-zinc-800)]',
'neutral' => '[--color-accent:var(--color-neutral-800)] [--color-accent-content:var(--color-neutral-800)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-white)] dark:[--color-accent-content:var(--color-white)] dark:[--color-accent-foreground:var(--color-neutral-800)]',
'stone' => '[--color-accent:var(--color-stone-800)] [--color-accent-content:var(--color-stone-800)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-white)] dark:[--color-accent-content:var(--color-white)] dark:[--color-accent-foreground:var(--color-stone-800)]',
'red' => '[--color-accent:var(--color-red-500)] [--color-accent-content:var(--color-red-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-red-500)] dark:[--color-accent-content:var(--color-red-400)] dark:[--color-accent-foreground:var(--color-white)]',
'orange' => '[--color-accent:var(--color-orange-500)] [--color-accent-content:var(--color-orange-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-orange-400)] dark:[--color-accent-content:var(--color-orange-400)] dark:[--color-accent-foreground:var(--color-orange-950)]',
'amber' => '[--color-accent:var(--color-amber-400)] [--color-accent-content:var(--color-amber-600)] [--color-accent-foreground:var(--color-amber-950)] dark:[--color-accent:var(--color-amber-400)] dark:[--color-accent-content:var(--color-amber-400)] dark:[--color-accent-foreground:var(--color-amber-950)]',
'yellow' => '[--color-accent:var(--color-yellow-400)] [--color-accent-content:var(--color-yellow-600)] [--color-accent-foreground:var(--color-yellow-950)] dark:[--color-accent:var(--color-yellow-400)] dark:[--color-accent-content:var(--color-yellow-400)] dark:[--color-accent-foreground:var(--color-yellow-950)]',
'lime' => '[--color-accent:var(--color-lime-400)] [--color-accent-content:var(--color-lime-600)] [--color-accent-foreground:var(--color-lime-900)] dark:[--color-accent:var(--color-lime-400)] dark:[--color-accent-content:var(--color-lime-400)] dark:[--color-accent-foreground:var(--color-lime-950)]',
'green' => '[--color-accent:var(--color-green-600)] [--color-accent-content:var(--color-green-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-green-600)] dark:[--color-accent-content:var(--color-green-400)] dark:[--color-accent-foreground:var(--color-white)]',
'emerald' => '[--color-accent:var(--color-emerald-600)] [--color-accent-content:var(--color-emerald-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-emerald-600)] dark:[--color-accent-content:var(--color-emerald-400)] dark:[--color-accent-foreground:var(--color-white)]',
'teal' => '[--color-accent:var(--color-teal-600)] [--color-accent-content:var(--color-teal-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-teal-600)] dark:[--color-accent-content:var(--color-teal-400)] dark:[--color-accent-foreground:var(--color-white)]',
'cyan' => '[--color-accent:var(--color-cyan-600)] [--color-accent-content:var(--color-cyan-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-cyan-600)] dark:[--color-accent-content:var(--color-cyan-400)] dark:[--color-accent-foreground:var(--color-white)]',
'sky' => '[--color-accent:var(--color-sky-600)] [--color-accent-content:var(--color-sky-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-sky-600)] dark:[--color-accent-content:var(--color-sky-400)] dark:[--color-accent-foreground:var(--color-white)]',
'blue' => '[--color-accent:var(--color-blue-500)] [--color-accent-content:var(--color-blue-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-blue-500)] dark:[--color-accent-content:var(--color-blue-400)] dark:[--color-accent-foreground:var(--color-white)]',
'indigo' => '[--color-accent:var(--color-indigo-500)] [--color-accent-content:var(--color-indigo-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-indigo-500)] dark:[--color-accent-content:var(--color-indigo-300)] dark:[--color-accent-foreground:var(--color-white)]',
'violet' => '[--color-accent:var(--color-violet-500)] [--color-accent-content:var(--color-violet-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-violet-500)] dark:[--color-accent-content:var(--color-violet-400)] dark:[--color-accent-foreground:var(--color-white)]',
'purple' => '[--color-accent:var(--color-purple-500)] [--color-accent-content:var(--color-purple-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-purple-500)] dark:[--color-accent-content:var(--color-purple-300)] dark:[--color-accent-foreground:var(--color-white)]',
'fuchsia' => '[--color-accent:var(--color-fuchsia-600)] [--color-accent-content:var(--color-fuchsia-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-fuchsia-600)] dark:[--color-accent-content:var(--color-fuchsia-400)] dark:[--color-accent-foreground:var(--color-white)]',
'pink' => '[--color-accent:var(--color-pink-600)] [--color-accent-content:var(--color-pink-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-pink-600)] dark:[--color-accent-content:var(--color-pink-400)] dark:[--color-accent-foreground:var(--color-white)]',
'rose' => '[--color-accent:var(--color-rose-500)] [--color-accent-content:var(--color-rose-500)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-rose-500)] dark:[--color-accent-content:var(--color-rose-400)] dark:[--color-accent-foreground:var(--color-white)]',
})
;
@endphp
<div {{ $attributes->class($classes) }}>
{{ $slot }}
</div>

View File

@@ -0,0 +1,23 @@
@aware([ 'transition', 'expanded' ])
@props([
'transition' => false,
'expanded' => false,
])
@php
$classes = Flux::classes()
->add('pt-2 text-sm text-zinc-500 dark:text-zinc-300')
;
@endphp
<div
x-show="open"
@if ($transition) x-collapse @endif
@if (! $expanded) x-cloak @endif
data-flux-accordion-content
>
<div {{ $attributes->class($classes) }}>
{{ $slot }}
</div>
</div>

View File

@@ -0,0 +1,33 @@
@aware([ 'disabled', 'variant' ])
@props([
'disabled' => null,
'variant' => null,
])
@php
$classes = Flux::classes()
->add('group/accordion-heading flex items-center w-full')
->add('text-start text-sm font-medium')
->add(match ($variant) {
default => 'justify-between [&>svg]:ms-6',
'reverse' => 'flex-row-reverse justify-end [&>svg]:me-2',
})
->add($disabled
? 'text-zinc-400 dark:text-zinc-400 cursor-default'
: 'text-zinc-800 dark:text-white cursor-pointer'
)
;
@endphp
<button type="button" {{ $attributes->class($classes) }} @if ($disabled) disabled @endif data-flux-accordion-heading>
<span class="flex-1">{{ $slot }}</span>
<?php if ($variant === 'reverse'): ?>
<flux:accordion.icon pointing="down" class="hidden group-data-open/accordion-heading:block text-zinc-800! dark:text-white!" />
<flux:accordion.icon pointing="right" class="block group-data-open/accordion-heading:hidden" />
<?php else: ?>
<flux:accordion.icon pointing="up" class="hidden group-data-open/accordion-heading:block text-zinc-800! dark:text-white!" />
<flux:accordion.icon pointing="down" class="block group-data-open/accordion-heading:hidden" />
<?php endif; ?>
</button>

View File

@@ -0,0 +1,17 @@
@aware([ 'disabled' ])
@props([
'pointing' => 'down',
'disabled' => null,
])
@php
$classes = Flux::classes()
->add('text-zinc-300 dark:text-zinc-400')
->add($disabled ? ''
: 'group-hover/accordion-heading:text-zinc-800 dark:group-hover/accordion-heading:text-white'
)
;
@endphp
<flux:icon :icon="'chevron-'.$pointing" variant="mini" aria-hidden="true" :attributes="$attributes->class($classes)" />

View File

@@ -0,0 +1,9 @@
@blaze
@props([
'variant' => null,
])
<ui-disclosure-group {{ $attributes->class('block') }} data-flux-accordion-heading>
{{ $slot }}
</ui-disclosure-group>

View File

@@ -0,0 +1,42 @@
@aware([ 'transition' ])
@props([
'transition' => false,
'disabled' => false,
'expanded' => false,
'heading' => null,
])
@php
// Support adding the .self modifier to the wire:model directive...
if (($wireModel = $attributes->wire('model')) && $wireModel->directive && ! $wireModel->hasModifier('self')) {
unset($attributes[$wireModel->directive]);
$wireModel->directive .= '.self';
$attributes = $attributes->merge([$wireModel->directive => $wireModel->value]);
}
// Support binding the state to a Livewire property
$state = $wireModel?->value ? '$wire.' . $wireModel->value : ($expanded ? 'true' : 'false');
$classes = Flux::classes()
->add('block pt-4 first:pt-0 pb-4 last:pb-0')
->add('border-b last:border-b-0 border-zinc-800/10 dark:border-white/10')
;
@endphp
<ui-disclosure
{{ $attributes->class($classes) }}
x-data="{ open: {{ $state }} }"
x-model.self="open"
data-flux-accordion-item
>
<?php if ($heading): ?>
<flux:accordion.heading>{{ $heading }}</flux:accordion.heading>
<flux:accordion.content>{{ $slot }}</flux:accordion.content>
<?php else: ?>
{{ $slot }}
<?php endif; ?>
</ui-disclosure>

View File

@@ -0,0 +1,21 @@
@blaze
@props([
'sticky' => null,
])
@php
$classes = Flux::classes('[grid-area:aside]');
if ($sticky) {
$attributes = $attributes->merge([
'x-data' => '',
'x-bind:style' => '{ position: \'sticky\', top: $el.offsetTop + \'px\', \'max-height\': \'calc(100dvh - \' + $el.offsetTop + \'px)\' }',
'class' => 'max-h-[100vh] overflow-y-auto',
]);
}
@endphp
<div {{ $attributes->class($classes) }} data-flux-aside>
{{ $slot }}
</div>

View File

@@ -0,0 +1,15 @@
@blaze
@props([
'filter' => true,
'disabled' => false,
])
<?php $containerAttributes = Flux::attributesAfter('container:', $attributes); ?>
<ui-select autocomplete clear="esc" data-flux-autocomplete {{ $containerAttributes->merge(['filter' => $filter, 'disabled' => $disabled]) }}>
<flux:input :attributes="$attributes->except('filter')" />
<flux:autocomplete.items>
{{ $slot }}
</flux:autocomplete.items>
</ui-select>

View File

@@ -0,0 +1,14 @@
@blaze
@php
$classes = Flux::classes()
->add('data-hidden:hidden flex items-center px-2 py-1.5 w-full focus:outline-hidden rounded-md')
->add('text-start text-sm font-medium')
->add('text-zinc-800 data-active:bg-zinc-100 dark:text-white dark:data-active:bg-zinc-600')
->add('scroll-my-[.3125rem]') // This is here so that when a user scrolls to the top or bottom of the list, the padding is included...
;
@endphp
<ui-option {{ $attributes->class($classes) }} data-flux-autocomplete-item>
{{ $slot }}
</ui-option>

View File

@@ -0,0 +1,17 @@
@blaze
@php
$classes = Flux::classes()
->add('[:where(&)]:max-h-[20rem]') // "[:where(&)]:" means it can be overriden without "!"...
->add('p-[.3125rem] overflow-y-auto rounded-lg shadow-xs')
->add('border border-zinc-200 dark:border-zinc-600')
->add('bg-white dark:bg-zinc-700')
->add('[&:not(:has(ui-empty[data-hidden]))]:hidden') // Hide this entire panel if there are no results...
;
@endphp
<ui-options popover="manual" {{ $attributes->class($classes) }} data-flux-autocomplete-items>
{{ $slot }}
<ui-empty class="contents"></ui-empty>
</ui-options>

View File

@@ -0,0 +1,13 @@
@blaze
@php
$classes = Flux::classes()
->add('flex isolate')
->add('*:not-first:-ml-2 **:ring-white **:dark:ring-zinc-900')
->add('**:data-[slot=avatar]:ring-4 **:data-[slot=avatar]:data-[size=sm]:ring-2 **:data-[slot=avatar]:data-[size=xs]:ring-2')
;
@endphp
<div {{ $attributes->class($classes) }}>
{{ $slot }}
</div>

View File

@@ -0,0 +1,189 @@
@blaze
@php $iconVariant ??= $attributes->pluck('icon:variant'); @endphp
@props([
'iconVariant' => 'solid',
'initials' => null,
'tooltip' => null,
'circle' => null,
'color' => null,
'badge' => null,
'name' => null,
'icon' => null,
'size' => 'md',
'src' => null,
'href' => null,
'alt' => null,
'as' => 'div',
])
@php
if ($name && ! $initials) {
$parts = explode(' ', trim($name));
if ($attributes->pluck('initials:single')) {
$initials = strtoupper(mb_substr($parts[0], 0, 1));
} else {
// Remove empty strings from the array...
$parts = collect($parts)->filter()->values()->all();
if (count($parts) > 1) {
$initials = strtoupper(mb_substr($parts[0], 0, 1) . mb_substr($parts[1], 0, 1));
} else if (count($parts) === 1) {
$initials = strtoupper(mb_substr($parts[0], 0, 1)) . strtolower(mb_substr($parts[0], 1, 1));
}
}
}
if ($name && $tooltip === true) {
$tooltip = $name;
}
$hasTextContent = $icon ?? $initials ?? $slot->isNotEmpty();
// If there's no text content, we'll fallback to using the user icon otherwise there will be an empty white square...
if (! $hasTextContent) {
$icon = 'user';
$hasTextContent = true;
}
// Be careful not to change the order of these colors.
// They're used in the hash function below and changing them would change actual user avatar colors that they might have grown to identify with.
$colors = ['red', 'orange', 'amber', 'yellow', 'lime', 'green', 'emerald', 'teal', 'cyan', 'sky', 'blue', 'indigo', 'violet', 'purple', 'fuchsia', 'pink', 'rose'];
if ($hasTextContent && $color === 'auto') {
$colorSeed = $attributes->pluck('color:seed') ?? $name ?? $icon ?? $initials ?? $slot;
$hash = crc32((string) $colorSeed);
$color = $colors[$hash % count($colors)];
}
$classes = Flux::classes()
->add(match($size) {
'xl' => '[:where(&)]:size-16 [:where(&)]:text-base',
'lg' => '[:where(&)]:size-12 [:where(&)]:text-base',
default => '[:where(&)]:size-10 [:where(&)]:text-sm',
'sm' => '[:where(&)]:size-8 [:where(&)]:text-sm',
'xs' => '[:where(&)]:size-6 [:where(&)]:text-xs',
})
->add($circle ? '[--avatar-radius:calc(infinity*1px)]' : match($size) {
'xl' => '[--avatar-radius:var(--radius-xl)]',
'lg' => '[--avatar-radius:var(--radius-lg)]',
default => '[--avatar-radius:var(--radius-lg)]',
'sm' => '[--avatar-radius:var(--radius-md)]',
'xs' => '[--avatar-radius:var(--radius-sm)]',
})
->add('relative flex-none isolate flex items-center justify-center')
->add('[:where(&)]:font-medium')
->add('rounded-[var(--avatar-radius)]')
->add($hasTextContent ? '[:where(&)]:bg-zinc-200 [:where(&)]:dark:bg-zinc-600 [:where(&)]:text-zinc-800 [:where(&)]:dark:text-white' : '')
->add(match($color) {
'red' => 'bg-red-200 text-red-800',
'orange' => 'bg-orange-200 text-orange-800',
'amber' => 'bg-amber-200 text-amber-800',
'yellow' => 'bg-yellow-200 text-yellow-800',
'lime' => 'bg-lime-200 text-lime-800',
'green' => 'bg-green-200 text-green-800',
'emerald' => 'bg-emerald-200 text-emerald-800',
'teal' => 'bg-teal-200 text-teal-800',
'cyan' => 'bg-cyan-200 text-cyan-800',
'sky' => 'bg-sky-200 text-sky-800',
'blue' => 'bg-blue-200 text-blue-800',
'indigo' => 'bg-indigo-200 text-indigo-800',
'violet' => 'bg-violet-200 text-violet-800',
'purple' => 'bg-purple-200 text-purple-800',
'fuchsia' => 'bg-fuchsia-200 text-fuchsia-800',
'pink' => 'bg-pink-200 text-pink-800',
'rose' => 'bg-rose-200 text-rose-800',
default => '',
})
->add(true ? [
'after:absolute after:inset-0 after:inset-ring-[1px] after:inset-ring-black/7 dark:after:inset-ring-white/10',
$circle ? 'after:rounded-full' : match($size) {
'xl' => 'after:rounded-xl',
'lg' => 'after:rounded-lg',
default => 'after:rounded-lg',
'sm' => 'after:rounded-md',
'xs' => 'after:rounded-sm',
},
] : []);
$iconClasses = Flux::classes()
->add('opacity-75')
->add(match($size) {
'lg' => 'size-8',
default => 'size-6',
'sm' => 'size-5',
'xs' => 'size-4',
});
$badgeColor = $attributes->pluck('badge:color') ?: (is_object($badge) ? $badge?->attributes?->pluck('color') : null);
$badgeCircle = $attributes->pluck('badge:circle') ?: (is_object($badge) ? $badge?->attributes?->pluck('circle') : null);
$badgePosition = $attributes->pluck('badge:position') ?: (is_object($badge) ? $badge?->attributes?->pluck('position') : null);
$badgeVariant = $attributes->pluck('badge:variant') ?: (is_object($badge) ? $badge?->attributes?->pluck('variant') : null);
$badgeClasses = Flux::classes()
->add('absolute ring-[2px] ring-white dark:ring-zinc-900 z-10')
->add(match($size) {
default => 'h-3 min-w-3',
'sm' => 'h-2 min-w-2',
'xs' => 'h-2 min-w-2',
})
->add('flex items-center justify-center tabular-nums overflow-hidden')
->add('text-[.625rem] text-zinc-800 dark:text-white font-medium')
->add($badgeCircle ? 'rounded-full' : 'rounded-[3px]')
->add($badgeVariant === 'outline' ? [
'after:absolute after:inset-[3px] after:bg-white dark:after:bg-zinc-900',
$badgeCircle ? 'after:rounded-full' : 'after:rounded-[1px]',
] : [])
->add(match($badgePosition) {
'top left' => 'top-0 left-0',
'top right' => 'top-0 right-0',
'bottom left' => 'bottom-0 left-0',
'bottom right' => 'bottom-0 right-0',
default => 'bottom-0 right-0',
})
->add(match($badgeColor) {
'red' => 'bg-red-500 dark:bg-red-400',
'orange' => 'bg-orange-500 dark:bg-orange-400',
'amber' => 'bg-amber-500 dark:bg-amber-400',
'yellow' => 'bg-yellow-500 dark:bg-yellow-400',
'lime' => 'bg-lime-500 dark:bg-lime-400',
'green' => 'bg-green-500 dark:bg-green-400',
'emerald' => 'bg-emerald-500 dark:bg-emerald-400',
'teal' => 'bg-teal-500 dark:bg-teal-400',
'cyan' => 'bg-cyan-500 dark:bg-cyan-400',
'sky' => 'bg-sky-500 dark:bg-sky-400',
'blue' => 'bg-blue-500 dark:bg-blue-400',
'indigo' => 'bg-indigo-500 dark:bg-indigo-400',
'violet' => 'bg-violet-500 dark:bg-violet-400',
'purple' => 'bg-purple-500 dark:bg-purple-400',
'fuchsia' => 'bg-fuchsia-500 dark:bg-fuchsia-400',
'pink' => 'bg-pink-500 dark:bg-pink-400',
'rose' => 'bg-rose-500 dark:bg-rose-400',
'zinc' => 'bg-zinc-400 dark:bg-zinc-300',
'gray' => 'bg-zinc-400 dark:bg-zinc-300',
default => 'bg-white dark:bg-zinc-900',
})
;
$label = $alt ?? $name;
@endphp
<flux:with-tooltip :$tooltip :$attributes>
<flux:button-or-link :attributes="$attributes->class($classes)->merge($circle ? ['data-circle' => 'true'] : [])" :$as :$href data-flux-avatar data-slot="avatar" data-size="{{ $size }}">
<?php if ($src): ?>
<img src="{{ $src }}" alt="{{ $alt ?? $name }}" class="rounded-[var(--avatar-radius)] size-full object-cover">
<?php elseif ($icon): ?>
<flux:icon :name="$icon" :variant="$iconVariant" :class="$iconClasses" />
<?php elseif ($hasTextContent): ?>
<span class="select-none">{{ $initials ?? $slot }}</span>
<?php endif; ?>
<?php if ($badge instanceof \Illuminate\View\ComponentSlot): ?>
<div {{ $badge->attributes->class($badgeClasses) }} aria-hidden="true">{{ $badge }}</div>
<?php elseif ($badge): ?>
<div class="{{ $badgeClasses }}" aria-hidden="true">{{ is_string($badge) ? $badge : '' }}</div>
<?php endif; ?>
</flux:button-or-link>
</flux:with-tooltip>

View File

@@ -0,0 +1,25 @@
@blaze
@php $iconVariant ??= $attributes->pluck('icon:variant'); @endphp
@props([
'iconVariant' => 'micro',
'icon' => 'x-mark',
])
@php
// When using the outline icon variant, we need to size it down to match the default icon sizes...
$iconClasses = Flux::classes()->add($iconVariant === 'outline' ? 'size-4' : '');
$classes = Flux::classes()
->add('p-1 -my-1 -me-1 opacity-50 hover:opacity-100')
;
@endphp
<button type="button" {{ $attributes->class($classes) }} data-flux-badge-close>
<?php if (is_string($icon) && $icon !== ''): ?>
<flux:icon :$icon :variant="$iconVariant" :class="$iconClasses" />
<?php else: ?>
{{ $icon }}
<?php endif; ?>
</button>

View File

@@ -0,0 +1,98 @@
@blaze
@php $iconTrailing ??= $attributes->pluck('icon:trailing'); @endphp
@php $iconVariant ??= $attributes->pluck('icon:variant'); @endphp
@props([
'iconVariant' => 'micro',
'iconTrailing' => null,
'variant' => null,
'color' => null,
'inset' => null,
'size' => null,
'icon' => null,
])
@php
$insetClasses = Flux::applyInset($inset, top: '-mt-1', right: '-me-2', bottom: '-mb-1', left: '-ms-2');
// When using the outline icon variant, we need to size it down to match the default icon sizes...
$iconClasses = Flux::classes()->add($iconVariant === 'outline' ? 'size-4' : '');
$classes = Flux::classes()
->add('inline-flex items-center font-medium whitespace-nowrap')
->add($insetClasses)
->add('[print-color-adjust:exact]')
->add(match ($size) {
'lg' => 'text-sm py-1.5 **:data-flux-badge-icon:me-2',
default => 'text-sm py-1 **:data-flux-badge-icon:me-1.5',
'sm' => 'text-xs py-1 **:data-flux-badge-icon:size-3 **:data-flux-badge-icon:me-1',
})
->add(match ($variant) {
'pill' => 'rounded-full px-3',
default => 'rounded-md px-2',
})
/**
* We can't compile classes for each color because of variants color to color and Tailwind's JIT compiler.
* We instead need to write out each one by hand. Sorry...
*/
->add($variant === 'solid' ? match ($color) {
default => 'text-text-primary bg-bg-elevated [&:is(button)]:hover:bg-bg-surface',
'red' => 'text-white dark:text-white bg-red-500 dark:bg-red-600 [&:is(button)]:hover:bg-red-600 dark:[button]:hover:bg-red-500',
'orange' => 'text-white dark:text-white bg-orange-500 dark:bg-orange-600 [&:is(button)]:hover:bg-orange-600 dark:[button]:hover:bg-orange-500',
'amber' => 'text-white dark:text-zinc-950 bg-amber-500 dark:bg-amber-500 [&:is(button)]:hover:bg-amber-600 dark:[button]:hover:bg-amber-400',
'yellow' => 'text-white dark:text-zinc-950 bg-yellow-500 dark:bg-yellow-400 [&:is(button)]:hover:bg-yellow-600 dark:[button]:hover:bg-yellow-300',
'lime' => 'text-white dark:text-white bg-lime-500 dark:bg-lime-600 [&:is(button)]:hover:bg-lime-600 dark:[button]:hover:bg-lime-500',
'green' => 'text-white dark:text-white bg-green-500 dark:bg-green-600 [&:is(button)]:hover:bg-green-600 dark:[button]:hover:bg-green-500',
'emerald' => 'text-white dark:text-white bg-emerald-500 dark:bg-emerald-600 [&:is(button)]:hover:bg-emerald-600 dark:[button]:hover:bg-emerald-500',
'teal' => 'text-white dark:text-white bg-teal-500 dark:bg-teal-600 [&:is(button)]:hover:bg-teal-600 dark:[button]:hover:bg-teal-500',
'cyan' => 'text-white dark:text-white bg-cyan-500 dark:bg-cyan-600 [&:is(button)]:hover:bg-cyan-600 dark:[button]:hover:bg-cyan-500',
'sky' => 'text-white dark:text-white bg-sky-500 dark:bg-sky-600 [&:is(button)]:hover:bg-sky-600 dark:[button]:hover:bg-sky-500',
'blue' => 'text-white dark:text-white bg-blue-500 dark:bg-blue-600 [&:is(button)]:hover:bg-blue-600 dark:[button]:hover:bg-blue-500',
'indigo' => 'text-white dark:text-white bg-indigo-500 dark:bg-indigo-600 [&:is(button)]:hover:bg-indigo-600 dark:[button]:hover:bg-indigo-500',
'violet' => 'text-white dark:text-white bg-violet-500 dark:bg-violet-600 [&:is(button)]:hover:bg-violet-600 dark:[button]:hover:bg-violet-500',
'purple' => 'text-white dark:text-white bg-purple-500 dark:bg-purple-600 [&:is(button)]:hover:bg-purple-600 dark:[button]:hover:bg-purple-500',
'fuchsia' => 'text-white dark:text-white bg-fuchsia-500 dark:bg-fuchsia-600 [&:is(button)]:hover:bg-fuchsia-600 dark:[button]:hover:bg-fuchsia-500',
'pink' => 'text-white dark:text-white bg-pink-500 dark:bg-pink-600 [&:is(button)]:hover:bg-pink-600 dark:[button]:hover:bg-pink-500',
'rose' => 'text-white dark:text-white bg-rose-500 dark:bg-rose-600 [&:is(button)]:hover:bg-rose-600 dark:[button]:hover:bg-rose-500',
} : match ($color) {
default => 'text-text-secondary [&_button]:text-text-secondary! bg-bg-elevated [&:is(button)]:hover:bg-bg-surface',
'red' => 'text-red-700 [&_button]:text-red-700! dark:text-red-200 dark:[&_button]:text-red-200! bg-red-400/20 dark:bg-red-400/40 [&:is(button)]:hover:bg-red-400/30 dark:[button]:hover:bg-red-400/50',
'orange' => 'text-orange-700 [&_button]:text-orange-700! dark:text-orange-200 dark:[&_button]:text-orange-200! bg-orange-400/20 dark:bg-orange-400/40 [&:is(button)]:hover:bg-orange-400/30 dark:[button]:hover:bg-orange-400/50',
'amber' => 'text-amber-700 [&_button]:text-amber-700! dark:text-amber-200 dark:[&_button]:text-amber-200! bg-amber-400/25 dark:bg-amber-400/40 [&:is(button)]:hover:bg-amber-400/40 dark:[button]:hover:bg-amber-400/50',
'yellow' => 'text-yellow-800 [&_button]:text-yellow-800! dark:text-yellow-200 dark:[&_button]:text-yellow-200! bg-yellow-400/25 dark:bg-yellow-400/40 [&:is(button)]:hover:bg-yellow-400/40 dark:[button]:hover:bg-yellow-400/50',
'lime' => 'text-lime-800 [&_button]:text-lime-800! dark:text-lime-200 dark:[&_button]:text-lime-200! bg-lime-400/25 dark:bg-lime-400/40 [&:is(button)]:hover:bg-lime-400/35 dark:[button]:hover:bg-lime-400/50',
'green' => 'text-green-800 [&_button]:text-green-800! dark:text-green-200 dark:[&_button]:text-green-200! bg-green-400/20 dark:bg-green-400/40 [&:is(button)]:hover:bg-green-400/30 dark:[button]:hover:bg-green-400/50',
'emerald' => 'text-emerald-800 [&_button]:text-emerald-800! dark:text-emerald-200 dark:[&_button]:text-emerald-200! bg-emerald-400/20 dark:bg-emerald-400/40 [&:is(button)]:hover:bg-emerald-400/30 dark:[button]:hover:bg-emerald-400/50',
'teal' => 'text-teal-800 [&_button]:text-teal-800! dark:text-teal-200 dark:[&_button]:text-teal-200! bg-teal-400/20 dark:bg-teal-400/40 [&:is(button)]:hover:bg-teal-400/30 dark:[button]:hover:bg-teal-400/50',
'cyan' => 'text-cyan-800 [&_button]:text-cyan-800! dark:text-cyan-200 dark:[&_button]:text-cyan-200! bg-cyan-400/20 dark:bg-cyan-400/40 [&:is(button)]:hover:bg-cyan-400/30 dark:[button]:hover:bg-cyan-400/50',
'sky' => 'text-sky-800 [&_button]:text-sky-800! dark:text-sky-200 dark:[&_button]:text-sky-200! bg-sky-400/20 dark:bg-sky-400/40 [&:is(button)]:hover:bg-sky-400/30 dark:[button]:hover:bg-sky-400/50',
'blue' => 'text-blue-800 [&_button]:text-blue-800! dark:text-blue-200 dark:[&_button]:text-blue-200! bg-blue-400/20 dark:bg-blue-400/40 [&:is(button)]:hover:bg-blue-400/30 dark:[button]:hover:bg-blue-400/50',
'indigo' => 'text-indigo-700 [&_button]:text-indigo-700! dark:text-indigo-200 dark:[&_button]:text-indigo-200! bg-indigo-400/20 dark:bg-indigo-400/40 [&:is(button)]:hover:bg-indigo-400/30 dark:[button]:hover:bg-indigo-400/50',
'violet' => 'text-violet-700 [&_button]:text-violet-700! dark:text-violet-200 dark:[&_button]:text-violet-200! bg-violet-400/20 dark:bg-violet-400/40 [&:is(button)]:hover:bg-violet-400/30 dark:[button]:hover:bg-violet-400/50',
'purple' => 'text-purple-700 [&_button]:text-purple-700! dark:text-purple-200 dark:[&_button]:text-purple-200! bg-purple-400/20 dark:bg-purple-400/40 [&:is(button)]:hover:bg-purple-400/30 dark:[button]:hover:bg-purple-400/50',
'fuchsia' => 'text-fuchsia-700 [&_button]:text-fuchsia-700! dark:text-fuchsia-200 dark:[&_button]:text-fuchsia-200! bg-fuchsia-400/20 dark:bg-fuchsia-400/40 [&:is(button)]:hover:bg-fuchsia-400/30 dark:[button]:hover:bg-fuchsia-400/50',
'pink' => 'text-pink-700 [&_button]:text-pink-700! dark:text-pink-200 dark:[&_button]:text-pink-200! bg-pink-400/20 dark:bg-pink-400/40 [&:is(button)]:hover:bg-pink-400/30 dark:[button]:hover:bg-pink-400/50',
'rose' => 'text-rose-700 [&_button]:text-rose-700! dark:text-rose-200 dark:[&_button]:text-rose-200! bg-rose-400/20 dark:bg-rose-400/40 [&:is(button)]:hover:bg-rose-400/30 dark:[button]:hover:bg-rose-400/50',
});
@endphp
<flux:button-or-div :attributes="$attributes->class($classes)" data-flux-badge>
<?php if (is_string($icon) && $icon !== ''): ?>
<flux:icon :$icon :variant="$iconVariant" :class="$iconClasses" data-flux-badge-icon />
<?php else: ?>
{{ $icon }}
<?php endif; ?>
{{ $slot }}
<?php if ($iconTrailing): ?>
<div class="ps-1 flex items-center" data-flux-badge-icon:trailing>
<?php if (is_string($iconTrailing)): ?>
<flux:icon :icon="$iconTrailing" :variant="$iconVariant" :class="$iconClasses" />
<?php else: ?>
{{ $iconTrailing }}
<?php endif; ?>
</div>
<?php endif; ?>
</flux:button-or-div>

View File

@@ -0,0 +1,63 @@
@blaze
@php $logoDark ??= $attributes->pluck('logo:dark'); @endphp
@props([
'name' => null,
'logo' => null,
'logoDark' => null,
'alt' => null,
'href' => '/',
])
@php
$classes = Flux::classes()
->add('h-10 flex items-center me-4')
;
$textClasses = Flux::classes()
->add('text-sm font-medium truncate [:where(&)]:text-zinc-800 dark:[:where(&)]:text-zinc-100')
;
@endphp
<?php if ($name): ?>
<a href="{{ $href }}" {{ $attributes->class([ $classes, 'gap-2' ]) }} data-flux-brand>
<?php if ($logo instanceof \Illuminate\View\ComponentSlot): ?>
<div {{ $logo->attributes->class('flex items-center justify-center [:where(&)]:h-6 [:where(&)]:min-w-6 [:where(&)]:rounded-sm overflow-hidden shrink-0') }}>
{{ $logo }}
</div>
<?php else: ?>
<div class="flex items-center justify-center h-6 rounded-sm overflow-hidden shrink-0">
<?php if ($logoDark): ?>
<img src="{{ $logo }}" alt="{{ $alt }}" class="h-6 dark:hidden" />
<img src="{{ $logoDark }}" alt="{{ $alt }}" class="h-6 hidden dark:block" />
<?php elseif ($logo): ?>
<img src="{{ $logo }}" alt="{{ $alt }}" class="h-6" />
<?php else: ?>
{{ $slot }}
<?php endif; ?>
</div>
<?php endif; ?>
<div class="{{ $textClasses }}">{{ $name }}</div>
</a>
<?php else: ?>
<a href="{{ $href }}" {{ $attributes->class($classes) }} data-flux-brand>
<?php if ($logo instanceof \Illuminate\View\ComponentSlot): ?>
<div {{ $logo->attributes->class('flex items-center justify-center [:where(&)]:h-6 [:where(&)]:min-w-6 [:where(&)]:rounded-sm overflow-hidden shrink-0') }}>
{{ $logo }}
</div>
<?php else: ?>
<div class="flex items-center justify-center h-6 rounded-sm overflow-hidden shrink-0">
<?php if ($logoDark): ?>
<img src="{{ $logo }}" alt="{{ $alt }}" class="h-6 dark:hidden" />
<img src="{{ $logoDark }}" alt="{{ $alt }}" class="h-6 hidden dark:block" />
<?php elseif ($logo): ?>
<img src="{{ $logo }}" alt="{{ $alt }}" class="h-6" />
<?php else: ?>
{{ $slot }}
<?php endif; ?>
</div>
<?php endif; ?>
</a>
<?php endif; ?>

View File

@@ -0,0 +1,5 @@
@blaze
<div {{ $attributes->class('flex') }} data-flux-breadcrumbs>
{{ $slot }}
</div>

View File

@@ -0,0 +1,69 @@
@blaze
@php $iconVariant ??= $attributes->pluck('icon:variant'); @endphp
@props([
'separator' => null,
'iconVariant' => 'mini',
'icon' => null,
'href' => null,
])
@php
$classes = Flux::classes()
->add('flex items-center')
->add('text-sm font-medium')
->add('group/breadcrumb')
;
$linkClasses = Flux::classes()
->add('text-zinc-800 dark:text-white')
->add('hover:underline decoration-zinc-800/20 underline-offset-4');
$staticTextClasses = Flux::classes()
->add('text-gray-500 dark:text-white/80')
;
$separatorClasses = Flux::classes()
->add('mx-1 text-zinc-300 dark:text-white/80')
->add('group-last/breadcrumb:hidden')
;
$iconClasses = Flux::classes()
// When using the outline icon variant, we need to size it down to match the default icon sizes...
->add($iconVariant === 'outline' ? 'size-5' : '')
;
[ $styleAttributes, $attributes ] = Flux::splitAttributes($attributes);
@endphp
<div {{ $styleAttributes->class($classes) }} data-flux-breadcrumbs-item>
<?php if ($href): ?>
<a {{ $attributes->class($linkClasses) }} href="{{ $href }}">
<?php if ($icon): ?>
<flux:icon :$icon :variant="$iconVariant" class="{{ $iconClasses }}" />
<?php else: ?>
{{ $slot }}
<?php endif; ?>
</a>
<?php else: ?>
<div {{ $attributes->class($staticTextClasses) }}>
<?php if ($icon): ?>
<flux:icon :$icon :variant="$iconVariant" class="{{ $iconClasses }}" />
<?php else: ?>
{{ $slot }}
<?php endif; ?>
</div>
<?php endif; ?>
@if ($separator == null)
<flux:icon icon="chevron-right" variant="mini" class="{{ $separatorClasses->add('rtl:hidden') }}" />
<flux:icon icon="chevron-left" variant="mini" class="{{ $separatorClasses->add('hidden rtl:inline') }}" />
@elseif (! is_string($separator))
{{ $separator }}
@elseif ($separator === 'slash')
<flux:icon icon="slash" variant="mini" class="{{ $separatorClasses->add('rtl:-scale-x-100') }}" />
@else
<flux:icon :icon="$separator" variant="mini" class="{{ $separatorClasses }}" />
@endif
</div>

View File

@@ -0,0 +1,47 @@
@blaze
@php
$classes = Flux::classes()
->add('flex group/button')
->add([
// With the external borders, let's always make sure the first and last children have outside borders.
// For internal borders, we will ensure that all left borders are removed, but the right borders remain.
// But when there is a input groupsuffix, then there should be no right internal border.
// That way we shouldn't ever have a double border...
// All inputs borders...
'[&>[data-flux-input]:last-child:not(:first-child)>[data-flux-group-target]:not([data-invalid])]:border-s-0',
'[&>[data-flux-input]:not(:first-child):not(:last-child)>[data-flux-group-target]:not([data-invalid])]:border-s-0',
'[&>[data-flux-input]:has(+[data-flux-input-group-suffix])>[data-flux-group-target]:not([data-invalid])]:border-e-0',
// Selects and date pickers borders...
'[&>*:last-child:not(:first-child)>[data-flux-group-target]:not([data-invalid])]:border-s-0',
'[&>*:not(:first-child):not(:last-child)>[data-flux-group-target]:not([data-invalid])]:border-s-0',
'[&>*:has(+[data-flux-input-group-suffix])>[data-flux-group-target]:not([data-invalid])]:border-e-0',
// Buttons borders...
'[&>[data-flux-group-target]:last-child:not(:first-child)]:border-s-0',
'[&>[data-flux-group-target]:not(:first-child):not(:last-child)]:border-s-0',
'[&>[data-flux-group-target]:has(+[data-flux-input-group-suffix])]:border-e-0',
// "Weld" the borders of inputs together by overriding their border radiuses...
'[&>[data-flux-group-target]:not(:first-child):not(:last-child)]:rounded-none',
'[&>[data-flux-group-target]:first-child:not(:last-child)]:rounded-e-none',
'[&>[data-flux-group-target]:last-child:not(:first-child)]:rounded-s-none',
// "Weld" borders for sub-children of group targets (button element inside ui-select element, etc.)...
'[&>*:not(:first-child):not(:last-child):not(:only-child)>[data-flux-group-target]]:rounded-none',
'[&>*:first-child:not(:last-child)>[data-flux-group-target]]:rounded-e-none',
'[&>*:last-child:not(:first-child)>[data-flux-group-target]]:rounded-s-none',
// "Weld" borders for sub-sub-children of group targets (input element inside div inside ui-select element (combobox))...
'[&>*:not(:first-child):not(:last-child):not(:only-child)>[data-flux-input]>[data-flux-group-target]]:rounded-none',
'[&>*:first-child:not(:last-child)>[data-flux-input]>[data-flux-group-target]]:rounded-e-none',
'[&>*:last-child:not(:first-child)>[data-flux-input]>[data-flux-group-target]]:rounded-s-none',
])
;
@endphp
<div {{ $attributes->class($classes) }} data-flux-button-group>
{{ $slot }}
</div>

View File

@@ -0,0 +1,202 @@
@blaze
@php $iconTrailing ??= $attributes->pluck('icon:trailing'); @endphp
@php $iconLeading ??= $attributes->pluck('icon:leading'); @endphp
@php $iconVariant ??= $attributes->pluck('icon:variant'); @endphp
@props([
'iconTrailing' => null,
'variant' => 'outline',
'iconVariant' => null,
'iconLeading' => null,
'type' => 'button',
'loading' => null,
'align' => 'center',
'size' => 'base',
'square' => null,
'color' => null,
'inset' => null,
'icon' => null,
'kbd' => null,
])
@php
$iconLeading = $icon ??= $iconLeading;
// Button should be a square if it has no text contents...
$square ??= $slot->isEmpty();
// Size-up icons in square/icon-only buttons... (xs buttons just get micro size/style...)
$iconVariant ??= ($size === 'xs')
? ($square ? 'micro' : 'micro')
: ($square ? 'mini' : 'micro');
$iconTrailingVariant ??= $attributes->pluck('icon-trailing:variant', $iconVariant);
// When using the outline icon variant, we need to size it down to match the default icon sizes...
$iconClasses = Flux::classes()
->add($iconVariant === 'outline' ? ($square && $size !== 'xs' ? 'size-5' : 'size-4') : '')
->add($attributes->pluck('icon:class'))
;
$iconTrailingClasses = Flux::classes()
->add($iconTrailingVariant === 'outline' ? ($square && $size !== 'xs' ? 'size-5' : 'size-4') : '')
->add($attributes->pluck('icon-trailing:class'))
;
$isTypeSubmitAndNotDisabledOnRender = $type === 'submit' && ! $attributes->has('disabled');
$isJsMethod = str_starts_with($attributes->whereStartsWith('wire:click')->first() ?? '', '$js.');
$loading ??= $loading ?? ($isTypeSubmitAndNotDisabledOnRender || $attributes->whereStartsWith('wire:click')->isNotEmpty() && ! $isJsMethod);
if ($loading && $type !== 'submit' && ! $isJsMethod) {
$attributes = $attributes->merge(['wire:loading.attr' => 'data-flux-loading']);
// We need to add `wire:target` here because without it the loading indicator won't be scoped
// by method params, causing multiple buttons with the same method but different params to
// trigger each other's loading indicators...
if (! $attributes->has('wire:target') && $target = $attributes->whereStartsWith('wire:click')->first()) {
$attributes = $attributes->merge(['wire:target' => $target], escape: false);
}
}
$classes = Flux::classes()
->add('relative items-center font-medium justify-center gap-2 whitespace-nowrap')
->add('disabled:opacity-75 dark:disabled:opacity-75 disabled:cursor-default disabled:pointer-events-none')
->add(match ($align) {
'start' => 'justify-start',
'center' => 'justify-center',
'end' => 'justify-end',
})
->add(match ($size) { // Size...
'base' => 'h-10 text-sm rounded-lg' . ' ' . (
$square
? 'w-10'
// If we have an icon, we want to reduce the padding on the side that has the icon...
: ($iconLeading && $iconLeading !== '' ? 'ps-3' : 'ps-4') . ' ' . ($iconTrailing && $iconTrailing !== '' ? 'pe-3' : 'pe-4')
),
'sm' => 'h-8 text-sm rounded-md' . ' ' . ($square ? 'w-8' : 'px-3'),
'xs' => 'h-6 text-xs rounded-md' . ' ' . ($square ? 'w-6' : 'px-2'),
})
->add('inline-flex') // Buttons are inline by default but links are blocks, so inline-flex is needed here to ensure link-buttons are displayed the same as buttons...
->add($inset ? match ($size) { // Inset...
'base' => $square
? Flux::applyInset($inset, top: '-mt-2.5', right: '-me-2.5', bottom: '-mb-2.5', left: '-ms-2.5')
: Flux::applyInset($inset, top: '-mt-2.5', right: '-me-4', bottom: '-mb-3', left: '-ms-4'),
'sm' => $square
? Flux::applyInset($inset, top: '-mt-1.5', right: '-me-1.5', bottom: '-mb-1.5', left: '-ms-1.5')
: Flux::applyInset($inset, top: '-mt-1.5', right: '-me-3', bottom: '-mb-1.5', left: '-ms-3'),
'xs' => $square
? Flux::applyInset($inset, top: '-mt-1', right: '-me-1', bottom: '-mb-1', left: '-ms-1')
: Flux::applyInset($inset, top: '-mt-1', right: '-me-2', bottom: '-mb-1', left: '-ms-2'),
} : '')
->add(match ($variant) { // Background color...
'primary' => $color ? 'bg-accent hover:bg-accent/90' : 'bg-orange-primary hover:bg-orange-light',
'filled' => 'bg-bg-elevated hover:bg-bg-surface',
'outline' => 'bg-transparent hover:bg-bg-elevated',
'danger' => 'bg-red-500 hover:bg-red-600',
'ghost' => 'bg-transparent hover:bg-bg-elevated',
'subtle' => 'bg-transparent hover:bg-bg-elevated',
})
->add(match ($variant) { // Text color...
'primary' => $color ? 'text-accent-foreground' : 'text-text-primary',
'filled' => 'text-text-primary',
'outline' => 'text-text-secondary hover:text-text-primary',
'danger' => 'text-white',
'ghost' => 'text-text-secondary hover:text-text-primary',
'subtle' => 'text-text-tertiary hover:text-text-primary',
})
->add(match ($variant) { // Border color...
'primary' => 'border-0',
'outline' => 'border border-border-default hover:border-border-default',
default => '',
})
->add(match ($variant) { // Shadows...
'primary' => '',
'danger' => '',
'outline' => '',
default => '',
})
->add(match ($variant) { // Grouped border treatments...
'ghost' => '',
'subtle' => '',
'outline' => '[[data-flux-button-group]_&]:border-s-0 [:is([data-flux-button-group]>&:first-child,_[data-flux-button-group]_:first-child>&)]:border-s-[1px]',
'filled' => '[[data-flux-button-group]_&]:border-e [:is([data-flux-button-group]>&:last-child,_[data-flux-button-group]_:last-child>&)]:border-e-0 [[data-flux-button-group]_&]:border-zinc-200/80 dark:[[data-flux-button-group]_&]:border-zinc-900/50',
'danger' => '[[data-flux-button-group]_&]:border-e [:is([data-flux-button-group]>&:last-child,_[data-flux-button-group]_:last-child>&)]:border-e-0 [[data-flux-button-group]_&]:border-red-600 dark:[[data-flux-button-group]_&]:border-red-900/25',
'primary' => '[[data-flux-button-group]_&]:border-e-0 [:is([data-flux-button-group]>&:last-child,_[data-flux-button-group]_:last-child>&)]:border-e-[1px] dark:[:is([data-flux-button-group]>&:last-child,_[data-flux-button-group]_:last-child>&)]:border-e-0 dark:[:is([data-flux-button-group]>&:last-child,_[data-flux-button-group]_:last-child>&)]:border-s-[1px] [:is([data-flux-button-group]>&:not(:first-child),_[data-flux-button-group]_:not(:first-child)>&)]:border-s-[color-mix(in_srgb,var(--color-accent-foreground),transparent_85%)]',
})
->add($loading ? [ // Loading states...
'*:transition-opacity',
$type === 'submit' ? '[&[disabled]>:not([data-flux-loading-indicator])]:opacity-0' : '[&[data-loading]>:not([data-flux-loading-indicator])]:opacity-0 [&[data-flux-loading]>:not([data-flux-loading-indicator])]:opacity-0',
$type === 'submit' ? '[&[disabled]>[data-flux-loading-indicator]]:opacity-100' : '[&[data-loading]>[data-flux-loading-indicator]]:opacity-100 [&[data-flux-loading]>[data-flux-loading-indicator]]:opacity-100',
$type === 'submit' ? '[&[disabled]]:pointer-events-none' : 'data-loading:pointer-events-none data-flux-loading:pointer-events-none',
] : [])
->add($variant === 'primary' ? match ($color) {
'slate' => '[--color-accent:var(--color-slate-800)] [--color-accent-content:var(--color-slate-800)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-white)] dark:[--color-accent-content:var(--color-white)] dark:[--color-accent-foreground:var(--color-slate-800)]',
'gray' => '[--color-accent:var(--color-gray-800)] [--color-accent-content:var(--color-gray-800)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-white)] dark:[--color-accent-content:var(--color-white)] dark:[--color-accent-foreground:var(--color-gray-800)]',
'zinc' => '[--color-accent:var(--color-zinc-800)] [--color-accent-content:var(--color-zinc-800)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-white)] dark:[--color-accent-content:var(--color-white)] dark:[--color-accent-foreground:var(--color-zinc-800)]',
'neutral' => '[--color-accent:var(--color-neutral-800)] [--color-accent-content:var(--color-neutral-800)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-white)] dark:[--color-accent-content:var(--color-white)] dark:[--color-accent-foreground:var(--color-neutral-800)]',
'stone' => '[--color-accent:var(--color-stone-800)] [--color-accent-content:var(--color-stone-800)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-white)] dark:[--color-accent-content:var(--color-white)] dark:[--color-accent-foreground:var(--color-stone-800)]',
'red' => '[--color-accent:var(--color-red-500)] [--color-accent-content:var(--color-red-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-red-500)] dark:[--color-accent-content:var(--color-red-400)] dark:[--color-accent-foreground:var(--color-white)]',
'orange' => '[--color-accent:var(--color-orange-500)] [--color-accent-content:var(--color-orange-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-orange-400)] dark:[--color-accent-content:var(--color-orange-400)] dark:[--color-accent-foreground:var(--color-orange-950)]',
'amber' => '[--color-accent:var(--color-amber-400)] [--color-accent-content:var(--color-amber-600)] [--color-accent-foreground:var(--color-amber-950)] dark:[--color-accent:var(--color-amber-400)] dark:[--color-accent-content:var(--color-amber-400)] dark:[--color-accent-foreground:var(--color-amber-950)]',
'yellow' => '[--color-accent:var(--color-yellow-400)] [--color-accent-content:var(--color-yellow-600)] [--color-accent-foreground:var(--color-yellow-950)] dark:[--color-accent:var(--color-yellow-400)] dark:[--color-accent-content:var(--color-yellow-400)] dark:[--color-accent-foreground:var(--color-yellow-950)]',
'lime' => '[--color-accent:var(--color-lime-400)] [--color-accent-content:var(--color-lime-600)] [--color-accent-foreground:var(--color-lime-900)] dark:[--color-accent:var(--color-lime-400)] dark:[--color-accent-content:var(--color-lime-400)] dark:[--color-accent-foreground:var(--color-lime-950)]',
'green' => '[--color-accent:var(--color-green-600)] [--color-accent-content:var(--color-green-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-green-600)] dark:[--color-accent-content:var(--color-green-400)] dark:[--color-accent-foreground:var(--color-white)]',
'emerald' => '[--color-accent:var(--color-emerald-600)] [--color-accent-content:var(--color-emerald-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-emerald-600)] dark:[--color-accent-content:var(--color-emerald-400)] dark:[--color-accent-foreground:var(--color-white)]',
'teal' => '[--color-accent:var(--color-teal-600)] [--color-accent-content:var(--color-teal-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-teal-600)] dark:[--color-accent-content:var(--color-teal-400)] dark:[--color-accent-foreground:var(--color-white)]',
'cyan' => '[--color-accent:var(--color-cyan-600)] [--color-accent-content:var(--color-cyan-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-cyan-600)] dark:[--color-accent-content:var(--color-cyan-400)] dark:[--color-accent-foreground:var(--color-white)]',
'sky' => '[--color-accent:var(--color-sky-600)] [--color-accent-content:var(--color-sky-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-sky-600)] dark:[--color-accent-content:var(--color-sky-400)] dark:[--color-accent-foreground:var(--color-white)]',
'blue' => '[--color-accent:var(--color-blue-500)] [--color-accent-content:var(--color-blue-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-blue-500)] dark:[--color-accent-content:var(--color-blue-400)] dark:[--color-accent-foreground:var(--color-white)]',
'indigo' => '[--color-accent:var(--color-indigo-500)] [--color-accent-content:var(--color-indigo-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-indigo-500)] dark:[--color-accent-content:var(--color-indigo-300)] dark:[--color-accent-foreground:var(--color-white)]',
'violet' => '[--color-accent:var(--color-violet-500)] [--color-accent-content:var(--color-violet-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-violet-500)] dark:[--color-accent-content:var(--color-violet-400)] dark:[--color-accent-foreground:var(--color-white)]',
'purple' => '[--color-accent:var(--color-purple-500)] [--color-accent-content:var(--color-purple-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-purple-500)] dark:[--color-accent-content:var(--color-purple-300)] dark:[--color-accent-foreground:var(--color-white)]',
'fuchsia' => '[--color-accent:var(--color-fuchsia-600)] [--color-accent-content:var(--color-fuchsia-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-fuchsia-600)] dark:[--color-accent-content:var(--color-fuchsia-400)] dark:[--color-accent-foreground:var(--color-white)]',
'pink' => '[--color-accent:var(--color-pink-600)] [--color-accent-content:var(--color-pink-600)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-pink-600)] dark:[--color-accent-content:var(--color-pink-400)] dark:[--color-accent-foreground:var(--color-white)]',
'rose' => '[--color-accent:var(--color-rose-500)] [--color-accent-content:var(--color-rose-500)] [--color-accent-foreground:var(--color-white)] dark:[--color-accent:var(--color-rose-500)] dark:[--color-accent-content:var(--color-rose-400)] dark:[--color-accent-foreground:var(--color-white)]',
default => '',
} : '')
;
// Exempt subtle and ghost buttons from receiving border roundness overrides from button.group...
$attributes = $attributes->merge([
'data-flux-group-target' => ! in_array($variant, ['subtle', 'ghost']),
]);
@endphp
<flux:with-tooltip :$attributes>
<flux:button-or-link-pure :$type :attributes="$attributes->class($classes)" data-flux-button>
<?php if ($loading): ?>
<div class="absolute inset-0 flex items-center justify-center opacity-0" data-flux-loading-indicator>
<flux:icon icon="loading" :variant="$iconVariant" :class="$iconClasses" />
</div>
<?php endif; ?>
<?php if (is_string($iconLeading) && $iconLeading !== ''): ?>
<flux:icon :icon="$iconLeading" :variant="$iconVariant" :class="$iconClasses" />
<?php elseif ($iconLeading): ?>
{{ $iconLeading }}
<?php endif; ?>
<?php if (($loading || $iconLeading || $iconTrailing) && ! $slot->isEmpty()): ?>
{{-- If we have a loading indicator, we need to wrap it in a span so it can be a target of *:opacity-0... --}}
{{-- Also, if we have an icon, we need to wrap it in a span so it can be recognized as a child of the button for :first-child selectors... --}}
<span>{{ $slot }}</span>
<?php else: ?>
{{ $slot }}
<?php endif; ?>
<?php if ($kbd): ?>
<div class="text-xs text-zinc-400 dark:text-zinc-400">{{ $kbd }}</div>
<?php endif; ?>
<?php if (is_string($iconTrailing) && $iconTrailing !== ''): ?>
{{-- Adding the extra margin class inline on the icon component below was causing a double up, so it needs to be added here first... --}}
<?php $iconClasses->add($square ? '' : '-ms-1'); ?>
<flux:icon :icon="$iconTrailing" :variant="$iconTrailingVariant" :class="$iconTrailingClasses" />
<?php elseif ($iconTrailing): ?>
{{ $iconTrailing }}
<?php endif; ?>
</flux:button-or-link-pure>
</flux:with-tooltip>

View File

@@ -0,0 +1,222 @@
@props([
'selectableHeader' => null,
'weekNumbers' => null,
'unavailable' => null,
'withInputs' => null,
'navigation' => null,
'withToday' => null,
'months' => null,
'value' => null,
'mode' => null,
'size' => null,
'name' => null,
])
@php
// We only want to show the name attribute if it has been set manually
// but not if it has been set from the `wire:model` attribute...
$showName = isset($name);
if (! isset($name)) {
$name = $attributes->whereStartsWith('wire:model')->first();
}
// Support adding the .self modifier to the wire:model directive...
if (($wireModel = $attributes->wire('model')) && $wireModel->directive && ! $wireModel->hasModifier('self')) {
unset($attributes[$wireModel->directive]);
$wireModel->directive .= '.self';
$attributes = $attributes->merge([$wireModel->directive => $wireModel->value]);
}
$months = $months ?? ($mode === 'range' ? 2 : 1);
$range = $mode === 'range';
// Mark it invalid if the property or any of it's nested attributes have errors...
$invalid ??= ($name && ($errors->has($name) || $errors->has($name . '.*')));
$class = Flux::classes()
->add('isolate relative')
;
$sizeClasses = match ($size) {
'2xl' => $weekNumbers ? 'size-12 sm:size-16' : 'size-13 sm:size-16',
'xl' => $weekNumbers ? 'size-11 sm:size-14' : 'size-12 sm:size-14',
'lg' => $weekNumbers ? 'size-10 sm:size-12' : 'size-11 sm:size-12',
default => $weekNumbers ? 'size-10 sm:size-11' : 'size-11 sm:size-11',
'sm' => $weekNumbers ? 'size-9 sm:size-10' : 'size-11 sm:size-10',
'xs' => $weekNumbers ? 'size-8 sm:size-9' : 'size-10 sm:size-9',
};
// Add support for `$value` being an array, if for example it's coming from
// the `old()` helper or if a user prefers to pass data in as an array...
if (is_array($value)) {
$value = match (true) {
$mode === 'range' => isset($value['start']) && isset($value['end']) ? $value['start'] . '/' . $value['end'] : null,
default => collect($value)->join(','),
};
}
if (isset($unavailable)) {
$unavailable = collect($unavailable)->implode(',');
}
@endphp
<ui-calendar
wire:ignore.children
{{ $attributes->class($class) }}
data-flux-calendar
@if ($mode) mode="{{ $mode }}" @endif
months="1"
sm:months="{{ $months }}"
@if (isset($unavailable) && $unavailable !== '') unavailable="{{ $unavailable }}" @endif
@if ($showName) name="{{ $name }}" @endif
@if (isset($value)) value="{{ $value }}" @endif
>
<?php if ($withInputs): ?>
<ui-calendar-inputs class="flex items-center p-2 border-b border-zinc-200 dark:border-white/10">
<?php if ($range): ?>
<div class="sm:px-2 flex items-center gap-4">
<div class="flex items-center gap-2"><span class="max-sm:hidden text-sm font-medium text-zinc-800 dark:text-white">{{ __('Start') }}</span> <flux:input type="date" class="w-[full] sm:w-[11.25rem]" /></div>
<div class="flex items-center gap-2"><span class="max-sm:hidden text-sm font-medium text-zinc-800 dark:text-white">{{ __('End') }}</span> <flux:input type="date" class="w-[full] sm:w-[11.25rem]" /></div>
</div>
<?php else: ?>
<flux:input type="date" class="w-full sm:w-[11.25rem]" />
<?php endif; ?>
</ui-calendar-inputs>
<?php endif; ?>
<div class="relative">
<div class="z-10 absolute top-0 inset-x-0 p-2">
<header class="flex justify-between items-center">
<div class="flex items-center gap-2">
<?php if ($selectableHeader): ?>
<ui-calendar-month display="short" class="font-medium text-sm text-zinc-800 dark:text-white">
<select
class="h-10 py-0 border-0 text-sm sm:h-8 appearance-none rounded-lg bg-zinc-100 dark:bg-white/10 dark:[&>option]:bg-zinc-700 dark:[&>option]:text-white px-3 sm:ps-2 [background-position:_right_.25rem_center_!important] rtl:[background-position:_left_.25rem_center_!important] pe-[1.35rem] bg-[length:16px_16px] bg-[url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2016%2016%22%20fill=%22%2300000040%22%20class=%22size-4%22%3E%3Cpath%20fill-rule=%22evenodd%22%20d=%22M4.22%206.22a.75.75%200%200%201%201.06%200L8%208.94l2.72-2.72a.75.75%200%201%201%201.06%201.06l-3.25%203.25a.75.75%200%200%201-1.06%200L4.22%207.28a.75.75%200%200%201%200-1.06Z%22%20clip-rule=%22evenodd%22/%3E%3C/svg%3E')] hover:bg-[length:16px_16px] hover:bg-[url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2016%2016%22%20fill=%22%231f2937%22%20class=%22size-4%22%3E%3Cpath%20fill-rule=%22evenodd%22%20d=%22M4.22%206.22a.75.75%200%200%201%201.06%200L8%208.94l2.72-2.72a.75.75%200%201%201%201.06%201.06l-3.25%203.25a.75.75%200%200%201-1.06%200L4.22%207.28a.75.75%200%200%201%200-1.06Z%22%20clip-rule=%22evenodd%22/%3E%3C/svg%3E')] dark:bg-[length:16px_16px] dark:bg-[url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2016%2016%22%20fill=%22%23ffffff75%22%20class=%22size-4%22%3E%3Cpath%20fill-rule=%22evenodd%22%20d=%22M4.22%206.22a.75.75%200%200%201%201.06%200L8%208.94l2.72-2.72a.75.75%200%201%201%201.06%201.06l-3.25%203.25a.75.75%200%200%201-1.06%200L4.22%207.28a.75.75%200%200%201%200-1.06Z%22%20clip-rule=%22evenodd%22/%3E%3C/svg%3E')] dark:hover:bg-[length:16px_16px] dark:hover:bg-[url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2016%2016%22%20fill=%22%23ffffff%22%20class=%22size-4%22%3E%3Cpath%20fill-rule=%22evenodd%22%20d=%22M4.22%206.22a.75.75%200%200%201%201.06%200L8%208.94l2.72-2.72a.75.75%200%201%201%201.06%201.06l-3.25%203.25a.75.75%200%200%201-1.06%200L4.22%207.28a.75.75%200%200%201%200-1.06Z%22%20clip-rule=%22evenodd%22/%3E%3C/svg%3E')] bg-no-repeat"
>
<template>
<option><slot></slot></option>
</template>
</select>
</ui-calendar-month>
<ui-calendar-year class="font-medium text-sm text-zinc-800 dark:text-white">
<select
class="h-10 py-0 border-0 text-sm sm:h-8 appearance-none rounded-lg bg-zinc-100 dark:bg-white/10 dark:[&>option]:bg-zinc-700 dark:[&>option]:text-white px-3 sm:ps-2 [background-position:_right_.25rem_center_!important] rtl:[background-position:_left_.25rem_center_!important] pe-[1.35rem] bg-[length:16px_16px] bg-[url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2016%2016%22%20fill=%22%2300000040%22%20class=%22size-4%22%3E%3Cpath%20fill-rule=%22evenodd%22%20d=%22M4.22%206.22a.75.75%200%200%201%201.06%200L8%208.94l2.72-2.72a.75.75%200%201%201%201.06%201.06l-3.25%203.25a.75.75%200%200%201-1.06%200L4.22%207.28a.75.75%200%200%201%200-1.06Z%22%20clip-rule=%22evenodd%22/%3E%3C/svg%3E')] hover:bg-[length:16px_16px] hover:bg-[url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2016%2016%22%20fill=%22%231f2937%22%20class=%22size-4%22%3E%3Cpath%20fill-rule=%22evenodd%22%20d=%22M4.22%206.22a.75.75%200%200%201%201.06%200L8%208.94l2.72-2.72a.75.75%200%201%201%201.06%201.06l-3.25%203.25a.75.75%200%200%201-1.06%200L4.22%207.28a.75.75%200%200%201%200-1.06Z%22%20clip-rule=%22evenodd%22/%3E%3C/svg%3E')] dark:bg-[length:16px_16px] dark:bg-[url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2016%2016%22%20fill=%22%23ffffff75%22%20class=%22size-4%22%3E%3Cpath%20fill-rule=%22evenodd%22%20d=%22M4.22%206.22a.75.75%200%200%201%201.06%200L8%208.94l2.72-2.72a.75.75%200%201%201%201.06%201.06l-3.25%203.25a.75.75%200%200%201-1.06%200L4.22%207.28a.75.75%200%200%201%200-1.06Z%22%20clip-rule=%22evenodd%22/%3E%3C/svg%3E')] dark:hover:bg-[length:16px_16px] dark:hover:bg-[url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2016%2016%22%20fill=%22%23ffffff%22%20class=%22size-4%22%3E%3Cpath%20fill-rule=%22evenodd%22%20d=%22M4.22%206.22a.75.75%200%200%201%201.06%200L8%208.94l2.72-2.72a.75.75%200%201%201%201.06%201.06l-3.25%203.25a.75.75%200%200%201-1.06%200L4.22%207.28a.75.75%200%200%201%200-1.06Z%22%20clip-rule=%22evenodd%22/%3E%3C/svg%3E')] bg-no-repeat"
>
<template>
<option><slot></slot></option>
</template>
</select>
</ui-calendar-year>
<?php endif; ?>
</div>
<div class="flex items-center">
<?php if ($withToday): ?>
<ui-calendar-today class="size-10 sm:size-8 rounded-lg flex items-center justify-center text-zinc-400 hover:bg-zinc-100 hover:text-zinc-800 dark:hover:bg-white/5 dark:hover:text-white [&[disabled]]:opacity-50 [&[disabled]]:pointer-events-none" aria-label="Previous month">
<div class="relative">
<template name="today">
<div class="cursor-default absolute inset-0 mt-[3px] flex items-center justify-center text-[.5625rem] font-semibold"><slot></slot></div>
</template>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.75 2C5.94891 2 6.13968 2.07902 6.28033 2.21967C6.42098 2.36032 6.5 2.55109 6.5 2.75V4H13.5V2.75C13.5 2.55109 13.579 2.36032 13.7197 2.21967C13.8603 2.07902 14.0511 2 14.25 2C14.4489 2 14.6397 2.07902 14.7803 2.21967C14.921 2.36032 15 2.55109 15 2.75V4H15.25C15.9793 4 16.6788 4.28973 17.1945 4.80546C17.7103 5.32118 18 6.02065 18 6.75V15.25C18 15.9793 17.7103 16.6788 17.1945 17.1945C16.6788 17.7103 15.9793 18 15.25 18H4.75C4.02065 18 3.32118 17.7103 2.80546 17.1945C2.28973 16.6788 2 15.9793 2 15.25V6.75C2 6.02065 2.28973 5.32118 2.80546 4.80546C3.32118 4.28973 4.02065 4 4.75 4H5V2.75C5 2.55109 5.07902 2.36032 5.21967 2.21967C5.36032 2.07902 5.55109 2 5.75 2ZM4.75 6.5C4.06 6.5 3.5 7.06 3.5 7.75V15.25C3.5 15.94 4.06 16.5 4.75 16.5H15.25C15.94 16.5 16.5 15.94 16.5 15.25V7.75C16.5 7.06 15.94 6.5 15.25 6.5H4.75Z" fill="currentColor"/>
</svg>
</div>
</ui-calendar-today>
<?php endif; ?>
<?php if ($navigation !== false): ?>
<ui-calendar-previous class="size-10 sm:size-8 rounded-lg flex items-center justify-center text-zinc-400 hover:bg-zinc-100 hover:text-zinc-800 dark:hover:bg-white/5 dark:hover:text-white [&[disabled]]:opacity-50 [&[disabled]]:pointer-events-none" aria-label="Previous month">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5 rtl:hidden"> <path fill-rule="evenodd" d="M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z" clip-rule="evenodd" /> </svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5 hidden rtl:block"> <path fill-rule="evenodd" d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" /> </svg>
</ui-calendar-previous>
<ui-calendar-next class="size-10 sm:size-8 rounded-lg flex items-center justify-center text-zinc-400 hover:bg-zinc-100 hover:text-zinc-800 dark:hover:bg-white/5 dark:hover:text-white [&[disabled]]:opacity-50 [&[disabled]]:pointer-events-none [&[disabled]_&]:text-zinc-400" aria-label="Next month">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5 rtl:hidden"> <path fill-rule="evenodd" d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" /> </svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5 hidden rtl:block"> <path fill-rule="evenodd" d="M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z" clip-rule="evenodd" /> </svg>
</ui-calendar-next>
<?php endif; ?>
</div>
</header>
</div>
</div>
<ui-calendar-months class="relative flex justify-center p-2 gap-4">
<template name="month">
<div>
<template name="heading">
<div class="@if ($selectableHeader) [[data-month]:first-of-type_&]:opacity-0 @endif mb-2 px-2 h-10 sm:h-8 flex items-center">
<div class="font-medium text-sm text-zinc-800 dark:text-white"><slot></slot></div>
</div>
</template>
<table>
<thead>
<tr class="flex w-full">
<?php if ($weekNumbers): ?>
<th scope="col" class="{{ $sizeClasses }} text-sm font-medium text-zinc-500 dark:text-zinc-300 flex items-center"><div class="w-full">#</div></th>
<?php endif; ?>
<template name="weekday">
<th scope="col" class="{{ $sizeClasses }} text-sm font-medium text-zinc-500 dark:text-zinc-300 flex items-center"><div class="w-full"><slot></slot></div></th>
</template>
</tr>
</thead>
<tbody>
<template name="week">
<tr class="flex w-full not-first-of-type:mt-1 [&:first-of-type_td[data-in-range]:not([data-selected]):first-child]:rounded-s-none [&:last-of-type_td[data-in-range]:not([data-selected]):last-child]:rounded-e-none">
<?php if ($weekNumbers): ?>
<template name="number">
<td class="p-0 relative {{ $sizeClasses }} text-xs font-medium text-zinc-400 flex items-center justify-center">
<slot></slot>
</td>
</template>
<?php endif; ?>
<template name="day">
<?php if ($attributes->has('static')): ?>
<td class="p-0 data-unavailable:line-through data-in-range:bg-zinc-100 dark:data-in-range:bg-white/10 data-start:rounded-s-lg data-end:rounded-e-lg data-end-preview:rounded-e-lg first-of-type:rounded-s-lg last-of-type:rounded-e-lg [&[data-selected]+[data-selected]]:rounded-s-none">
<div class="relative {{ $sizeClasses }} text-sm font-medium text-zinc-800 dark:text-white flex items-center justify-center rounded-lg [td[data-selected]:has(+td[data-selected])_&]:rounded-e-none [td[data-selected]+td[data-selected]_&]:rounded-s-none [td[data-selected]_&]:bg-[var(--color-accent)] [td[data-selected]_&]:text-[var(--color-accent-foreground)] [td[data-selected]_&[disabled]]:opacity-50 [td[disabled]_&]:text-zinc-400 [td[disabled]_&]:pointer-events-none [td[disabled]_&]:cursor-default">
<div class="absolute inset-0 hidden [td[data-today]_&]:flex justify-center items-end"><div class="mb-1 size-1 rounded-full bg-zinc-800 dark:bg-white [td[data-selected]_&]:bg-white dark:[td[data-selected]_&]:bg-zinc-800"></div></div>
<slot></slot>
</div>
</td>
<?php else: ?>
<td class="_max-sm:data-outside:opacity-0 p-0 data-unavailable:line-through data-in-range:bg-zinc-100 dark:data-in-range:bg-white/10 data-start:rounded-s-lg data-end:rounded-e-lg data-end-preview:rounded-e-lg first-of-type:rounded-s-lg last-of-type:rounded-e-lg [&[data-selected]+[data-selected]]:rounded-s-none [[data-in-range]:not([data-selected]):not([data-end-preview])+&[data-outside]]:bg-linear-to-r [&[data-outside]:has(+[data-in-range])]:bg-linear-to-l rtl:[[data-in-range]:not([data-selected]):not([data-end-preview])+&[data-outside]]:bg-linear-to-l rtl:[&[data-outside]:has(+[data-in-range])]:bg-linear-to-r data-outside:opacity-50 from-zinc-100 dark:from-white/10 from-1% [&[data-outside]:has(+[data-in-range][data-selected])]:bg-none!">
<ui-tooltip position="top">
<button type="button" class="{{ $sizeClasses }} text-sm font-medium text-zinc-800 dark:text-white flex flex-col items-center justify-center rounded-lg hover:bg-zinc-800/5 dark:hover:bg-white/5 [td[data-selected]:has(+td[data-selected])_&]:rounded-e-none [td[data-selected]+td[data-selected]_&]:rounded-s-none [td[data-selected]_&]:bg-[var(--color-accent)] [td[data-selected]_&]:text-[var(--color-accent-foreground)] [td[data-selected]_&[disabled]]:opacity-50 disabled:text-zinc-400 disabled:pointer-events-none disabled:cursor-default [[readonly]_&]:pointer-events-none [[readonly]_&]:cursor-default [[readonly]_&]:bg-transparent">
<div class="relative">
<div class="absolute inset-x-0 bottom-[-3px] hidden [td[data-today]_&]:flex justify-center items-end"><div class="size-1 rounded-full bg-zinc-800 dark:bg-white [td[data-selected]_&]:bg-white dark:[td[data-selected]_&]:bg-zinc-800"></div></div>
<div><slot></slot></div>
<template name="subtext">
<div class="absolute inset-x-0 bottom-[-1rem] flex justify-center font-medium text-xs text-zinc-400 dark:text-zinc-500 [[data-date-variant='success']_&]:text-lime-600 dark:[[data-date-variant='success']_&]:text-lime-400 [[data-date-variant='warning']_&]:text-yellow-600 dark:[[data-date-variant='warning']_&]:text-yellow-400 [[data-date-variant='danger']_&]:text-rose-500 dark:[[data-date-variant='danger']_&]:text-rose-400">
<slot></slot>
</div>
</template>
</div>
</button>
<template name="details">
<div popover="manual" class="relative py-2 px-2.5 rounded-md text-xs text-white font-medium bg-zinc-800 dark:bg-zinc-700 dark:border dark:border-white/10 p-0 overflow-visible">
<slot></slot>
</div>
</template>
</ui-tooltip>
</td>
<?php endif; ?>
</template>
</tr>
</template>
</tbody>
</table>
</div>
</template>
</ui-calendar-months>
</ui-calendar>

View File

@@ -0,0 +1,29 @@
@blaze
@php $iconVariant ??= $attributes->pluck('icon:variant'); @endphp
@props([
'iconVariant' => 'mini',
'icon' => null,
])
@php
$classes = Flux::classes()
->add('flex items-center gap-2 text-sm font-medium')
;
$iconClasses = Flux::classes()
->add('inline-block size-5 text-[var(--callout-icon)] dark:text-[var(--callout-icon)]')
->add($attributes->pluck('class:icon'))
;
@endphp
<div {{ $attributes->class($classes) }} data-slot="heading">
<?php if (is_string($icon) && $icon !== ''): ?>
<flux:icon :icon="$icon" :variant="$iconVariant" :class="$iconClasses" />
<?php elseif ($icon): ?>
{{ $icon }}
<?php endif; ?>
{{ $slot }}
</div>

View File

@@ -0,0 +1,222 @@
@blaze
@php $iconVariant ??= $attributes->pluck('icon:variant'); @endphp
@props([
'iconVariant' => 'mini',
'controls' => null,
'heading' => null,
'color' => 'white',
'variant' => null,
'actions' => null,
'content' => null,
'inline' => null,
'text' => null,
'icon' => null,
])
@php
if ($color === 'gray') $color = 'zinc';
$color = match($variant) {
'success' => 'green',
'danger' => 'red',
'warning' => 'yellow',
'secondary' => 'zinc',
default => $color,
};
$classes = Flux::classes()
->add('@container p-2 flex border rounded-xl')
->add([
'border-(--callout-border) bg-(--callout-background)',
'[&_[data-slot=heading]]:text-(--callout-heading)',
'[&_[data-slot=text]]:text-(--callout-text)',
])
->add(match($color) {
'blue' => [
'[--callout-border:var(--color-blue-200)] dark:[--callout-border:color-mix(in_oklab,var(--color-blue-400),transparent_50%)]',
'[--callout-background:var(--color-blue-50)] dark:[--callout-background:color-mix(in_oklab,var(--color-blue-400),transparent_90%)]',
'[--callout-heading:var(--color-blue-600)] dark:[--callout-heading:var(--color-blue-200)]',
'[--callout-text:var(--color-blue-600)] dark:[--callout-text:var(--color-blue-300)]',
'[--callout-icon:var(--color-blue-500)] dark:[--callout-icon:var(--color-blue-400)]',
],
'sky' => [
'[--callout-border:var(--color-sky-200)] dark:[--callout-border:color-mix(in_oklab,var(--color-sky-400),transparent_50%)]',
'[--callout-background:var(--color-sky-50)] dark:[--callout-background:color-mix(in_oklab,var(--color-sky-400),transparent_90%)]',
'[--callout-heading:var(--color-sky-600)] dark:[--callout-heading:var(--color-sky-200)]',
'[--callout-text:var(--color-sky-600)] dark:[--callout-text:var(--color-sky-300)]',
'[--callout-icon:var(--color-sky-500)] dark:[--callout-icon:var(--color-sky-400)]',
],
'red' => [
'[--callout-border:var(--color-red-200)] dark:[--callout-border:color-mix(in_oklab,var(--color-red-400),transparent_50%)]',
'[--callout-background:var(--color-red-50)] dark:[--callout-background:color-mix(in_oklab,var(--color-red-400),transparent_90%)]',
'[--callout-heading:var(--color-red-700)] dark:[--callout-heading:var(--color-red-200)]',
'[--callout-text:var(--color-red-700)] dark:[--callout-text:var(--color-red-300)]',
'[--callout-icon:var(--color-red-400)] dark:[--callout-icon:var(--color-red-400)]',
],
'orange' => [
'[--callout-border:var(--color-orange-200)] dark:[--callout-border:color-mix(in_oklab,var(--color-orange-400),transparent_50%)]',
'[--callout-background:var(--color-orange-50)] dark:[--callout-background:color-mix(in_oklab,var(--color-orange-400),transparent_90%)]',
'[--callout-heading:var(--color-orange-600)] dark:[--callout-heading:var(--color-orange-200)]',
'[--callout-text:var(--color-orange-600)] dark:[--callout-text:var(--color-orange-300)]',
'[--callout-icon:var(--color-orange-500)] dark:[--callout-icon:var(--color-orange-400)]',
],
'amber' => [
'[--callout-border:var(--color-amber-400)] dark:[--callout-border:color-mix(in_oklab,var(--color-amber-400),transparent_50%)]',
'[--callout-background:var(--color-amber-50)] dark:[--callout-background:color-mix(in_oklab,var(--color-amber-400),transparent_90%)]',
'[--callout-heading:var(--color-amber-600)] dark:[--callout-heading:var(--color-amber-200)]',
'[--callout-text:var(--color-amber-600)] dark:[--callout-text:var(--color-amber-300)]',
'[--callout-icon:var(--color-amber-500)] dark:[--callout-icon:var(--color-amber-400)]',
],
'yellow' => [
'[--callout-border:var(--color-yellow-400)] dark:[--callout-border:color-mix(in_oklab,var(--color-yellow-400),transparent_50%)]',
'[--callout-background:var(--color-yellow-50)] dark:[--callout-background:color-mix(in_oklab,var(--color-yellow-400),transparent_90%)]',
'[--callout-heading:var(--color-yellow-600)] dark:[--callout-heading:var(--color-yellow-200)]',
'[--callout-text:var(--color-yellow-700)] dark:[--callout-text:var(--color-yellow-300)]',
'[--callout-icon:var(--color-yellow-500)] dark:[--callout-icon:var(--color-yellow-400)]',
],
'lime' => [
'[--callout-border:var(--color-lime-400)] dark:[--callout-border:color-mix(in_oklab,var(--color-lime-400),transparent_50%)]',
'[--callout-background:var(--color-lime-50)] dark:[--callout-background:color-mix(in_oklab,var(--color-lime-400),transparent_90%)]',
'[--callout-heading:var(--color-lime-700)] dark:[--callout-heading:var(--color-lime-200)]',
'[--callout-text:var(--color-lime-600)] dark:[--callout-text:var(--color-lime-300)]',
'[--callout-icon:var(--color-lime-500)] dark:[--callout-icon:var(--color-lime-400)]',
],
'green' => [
'[--callout-border:var(--color-green-300)] dark:[--callout-border:color-mix(in_oklab,var(--color-green-400),transparent_50%)]',
'[--callout-background:var(--color-green-50)] dark:[--callout-background:color-mix(in_oklab,var(--color-green-400),transparent_90%)]',
'[--callout-heading:var(--color-green-600)] dark:[--callout-heading:var(--color-green-200)]',
'[--callout-text:var(--color-green-600)] dark:[--callout-text:var(--color-green-300)]',
'[--callout-icon:var(--color-green-500)] dark:[--callout-icon:var(--color-green-400)]',
],
'emerald' => [
'[--callout-border:var(--color-emerald-200)] dark:[--callout-border:color-mix(in_oklab,var(--color-emerald-400),transparent_50%)]',
'[--callout-background:var(--color-emerald-50)] dark:[--callout-background:color-mix(in_oklab,var(--color-emerald-400),transparent_90%)]',
'[--callout-heading:var(--color-emerald-600)] dark:[--callout-heading:var(--color-emerald-200)]',
'[--callout-text:var(--color-emerald-600)] dark:[--callout-text:var(--color-emerald-300)]',
'[--callout-icon:var(--color-emerald-500)] dark:[--callout-icon:var(--color-emerald-400)]',
],
'teal' => [
'[--callout-border:var(--color-teal-200)] dark:[--callout-border:color-mix(in_oklab,var(--color-teal-400),transparent_50%)]',
'[--callout-background:var(--color-teal-50)] dark:[--callout-background:color-mix(in_oklab,var(--color-teal-400),transparent_90%)]',
'[--callout-heading:var(--color-teal-600)] dark:[--callout-heading:var(--color-teal-200)]',
'[--callout-text:var(--color-teal-600)] dark:[--callout-text:var(--color-teal-300)]',
'[--callout-icon:var(--color-teal-500)] dark:[--callout-icon:var(--color-teal-400)]',
],
'cyan' => [
'[--callout-border:var(--color-cyan-200)] dark:[--callout-border:color-mix(in_oklab,var(--color-cyan-400),transparent_50%)]',
'[--callout-background:var(--color-cyan-50)] dark:[--callout-background:color-mix(in_oklab,var(--color-cyan-400),transparent_90%)]',
'[--callout-heading:var(--color-cyan-600)] dark:[--callout-heading:var(--color-cyan-200)]',
'[--callout-text:var(--color-cyan-600)] dark:[--callout-text:var(--color-cyan-300)]',
'[--callout-icon:var(--color-cyan-500)] dark:[--callout-icon:var(--color-cyan-400)]',
],
'indigo' => [
'[--callout-border:var(--color-indigo-200)] dark:[--callout-border:color-mix(in_oklab,var(--color-indigo-400),transparent_50%)]',
'[--callout-background:var(--color-indigo-50)] dark:[--callout-background:color-mix(in_oklab,var(--color-indigo-400),transparent_90%)]',
'[--callout-heading:var(--color-indigo-600)] dark:[--callout-heading:var(--color-indigo-200)]',
'[--callout-text:var(--color-indigo-600)] dark:[--callout-text:var(--color-indigo-300)]',
'[--callout-icon:var(--color-indigo-500)] dark:[--callout-icon:var(--color-indigo-400)]',
],
'violet' => [
'[--callout-border:var(--color-violet-200)] dark:[--callout-border:color-mix(in_oklab,var(--color-violet-400),transparent_50%)]',
'[--callout-background:var(--color-violet-50)] dark:[--callout-background:color-mix(in_oklab,var(--color-violet-400),transparent_90%)]',
'[--callout-heading:var(--color-violet-600)] dark:[--callout-heading:var(--color-violet-200)]',
'[--callout-text:var(--color-violet-600)] dark:[--callout-text:var(--color-violet-300)]',
'[--callout-icon:var(--color-violet-500)] dark:[--callout-icon:var(--color-violet-400)]',
],
'purple' => [
'[--callout-border:var(--color-purple-300)] dark:[--callout-border:color-mix(in_oklab,var(--color-purple-400),transparent_50%)]',
'[--callout-background:var(--color-purple-50)] dark:[--callout-background:color-mix(in_oklab,var(--color-purple-400),transparent_90%)]',
'[--callout-heading:var(--color-purple-800)] dark:[--callout-heading:var(--color-purple-200)]',
'[--callout-text:var(--color-purple-700)] dark:[--callout-text:var(--color-purple-300)]',
'[--callout-icon:var(--color-purple-500)] dark:[--callout-icon:var(--color-purple-400)]',
],
'fuchsia' => [
'[--callout-border:var(--color-fuchsia-200)] dark:[--callout-border:color-mix(in_oklab,var(--color-fuchsia-400),transparent_50%)]',
'[--callout-background:var(--color-fuchsia-50)] dark:[--callout-background:color-mix(in_oklab,var(--color-fuchsia-400),transparent_90%)]',
'[--callout-heading:var(--color-fuchsia-600)] dark:[--callout-heading:var(--color-fuchsia-200)]',
'[--callout-text:var(--color-fuchsia-600)] dark:[--callout-text:var(--color-fuchsia-300)]',
'[--callout-icon:var(--color-fuchsia-500)] dark:[--callout-icon:var(--color-fuchsia-400)]',
],
'pink' => [
'[--callout-border:var(--color-pink-200)] dark:[--callout-border:color-mix(in_oklab,var(--color-pink-400),transparent_50%)]',
'[--callout-background:var(--color-pink-50)] dark:[--callout-background:color-mix(in_oklab,var(--color-pink-400),transparent_90%)]',
'[--callout-heading:var(--color-pink-600)] dark:[--callout-heading:var(--color-pink-200)]',
'[--callout-text:var(--color-pink-600)] dark:[--callout-text:var(--color-pink-300)]',
'[--callout-icon:var(--color-pink-500)] dark:[--callout-icon:var(--color-pink-400)]',
],
'rose' => [
'[--callout-border:var(--color-rose-200)] dark:[--callout-border:color-mix(in_oklab,var(--color-rose-400),transparent_50%)]',
'[--callout-background:var(--color-rose-50)] dark:[--callout-background:color-mix(in_oklab,var(--color-rose-400),transparent_90%)]',
'[--callout-heading:var(--color-rose-600)] dark:[--callout-heading:var(--color-rose-200)]',
'[--callout-text:var(--color-rose-600)] dark:[--callout-text:var(--color-rose-300)]',
'[--callout-icon:var(--color-rose-500)] dark:[--callout-icon:var(--color-rose-400)]',
],
'zinc' => [
'[--callout-border:var(--color-zinc-200)] dark:[--callout-border:color-mix(in_oklab,var(--color-white),transparent_95%)]',
'[--callout-background:var(--color-zinc-50)] dark:[--callout-background:color-mix(in_oklab,var(--color-zinc-400),transparent_90%)]',
'[--callout-heading:var(--color-zinc-800)] dark:[--callout-heading:var(--color-zinc-200)]',
'[--callout-text:var(--color-zinc-500)] dark:[--callout-text:var(--color-zinc-300)]',
'[--callout-icon:var(--color-zinc-400)] dark:[--callout-icon:var(--color-zinc-400)]',
],
default => [
'[--callout-border:var(--color-zinc-200)] dark:[--callout-border:color-mix(in_oklab,var(--color-white),transparent_95%)]',
'[--callout-background:var(--color-white)] dark:[--callout-background:color-mix(in_oklab,var(--color-zinc-400),transparent_90%)]',
'[--callout-heading:var(--color-zinc-800)] dark:[--callout-heading:var(--color-zinc-200)]',
'[--callout-text:var(--color-zinc-500)] dark:[--callout-text:var(--color-zinc-300)]',
'[--callout-icon:var(--color-zinc-400)] dark:[--callout-icon:var(--color-zinc-400)]',
],
})
;
$iconWrapperClasses = Flux::classes()
->add('ps-2 py-2 pe-0 flex items-baseline')
;
$iconClasses = Flux::classes()
->add('inline-block size-5 text-[var(--callout-icon)] dark:text-[var(--callout-icon)]')
->add($attributes->pluck('class:icon'))
;
@endphp
<div {{ $attributes->class($classes) }} data-flux-callout>
<?php if (is_string($icon) && $icon !== ''): ?>
<div class="{{ $iconWrapperClasses }}">
<flux:icon :icon="$icon" :variant="$iconVariant" :class="$iconClasses" />
</div>
<?php elseif ($icon): ?>
<div {{ $icon->attributes->class($iconWrapperClasses) }}>
{{ $icon }}
</div>
<?php endif; ?>
<div class="ps-2 flex-1 {{ $inline ? '@md:flex @md:[&>[data-slot="content"]:has([data-slot="heading"]):has([data-slot="text"])+[data-slot="actions"]]:p-2' : '' }}">
<div class="flex-1 py-2 pe-3 @md:pe-4 flex flex-col justify-center gap-2" data-slot="content">
<?php if ($heading): ?>
<flux:callout.heading>{{ $heading }}</flux:callout.heading>
<?php endif; ?>
<?php if ($text): ?>
<flux:callout.text>{{ $text }}</flux:callout.text>
<?php endif; ?>
{{ $content ?? $slot }}
</div>
<?php if ($actions): ?>
<div {{ $actions->attributes->class([
$inline ? '@max-md:py-2 @md:m-[-2px] @md:ps-4 @md:justify-end @md:flex-row-reverse' : 'py-2',
'self-start flex items-center gap-2'
]) }} data-slot="actions">
{{ $actions }}
</div>
<?php endif; ?>
</div>
<?php if ($controls): ?>
<div {{ $controls->attributes->class($inline ? 'ps-2 m-[-2px]' : 'ps-2') }}>
{{ $controls }}
</div>
<?php endif; ?>
</div>

View File

@@ -0,0 +1,15 @@
@blaze
@props([
'external' => null,
])
@php
$classes = Flux::classes()
->add('inline font-medium')
->add('underline underline-offset-[6px] hover:decoration-current')
->add('decoration-zinc-800/20 dark:decoration-white/20')
;
@endphp
{{-- NOTE: It's important that this file has NO newline at the end of the file. --}}
<a {{ $attributes->class($classes) }} <?php if ($external) : ?>target="_blank"<?php endif; ?>>{{ $slot }}</a>

View File

@@ -0,0 +1,5 @@
@blaze
<div {{ $attributes->class('text-sm') }} data-slot="text">
{{ $slot }}
</div>

View File

@@ -0,0 +1,20 @@
@blaze
@props([
'size' => null,
])
@php
$classes = Flux::classes()
->add('[:where(&)]:bg-bg-surface')
->add('border border-border-subtle')
->add(match ($size) {
default => '[:where(&)]:p-6 [:where(&)]:rounded-xl',
'sm' => '[:where(&)]:p-4 [:where(&)]:rounded-lg',
})
;
@endphp
<div {{ $attributes->class($classes) }} data-flux-card>
{{ $slot }}
</div>

View File

@@ -0,0 +1,9 @@
@aware(['field'])
@props([
'field' => 'value',
])
<template name="area" field="{{ $field }}" {{ $attributes->only(['curve']) }}>
<path {{ $attributes->except('curve')->merge(['fill' => 'currentColor']) }}></path>
</template>

View File

@@ -0,0 +1,21 @@
@aware(['axis' => 'x'])
@if ($axis === 'x')
<template name="grid-line">
<line {{ $attributes->merge([
'type' => 'horizontal',
'class' => 'text-zinc-200/50 dark:text-white/15',
'stroke' => 'currentColor',
'stroke-width' => '1',
]) }}></line>
</template>
@else
<template name="grid-line">
<line {{ $attributes->merge([
'type' => 'vertical',
'class' => 'text-zinc-200/50 dark:text-white/15',
'stroke' => 'currentColor',
'stroke-width' => '1',
]) }}></line>
</template>
@endif

View File

@@ -0,0 +1,25 @@
@blaze
@props([
'axis' => 'x',
'format' => null,
'field' => 'index',
'position' => null,
'tickValues' => null,
])
@php
$format = is_array($format) ? \Illuminate\Support\Js::encode($format) : $format;
$field ??= $axis === 'x' ? 'date' : $field;
@endphp
<template {{ $attributes->merge([
'name' => 'axis',
'axis' => $axis,
'format' => $format,
'position' => $position,
'tick-values' => is_string($tickValues) ? $tickValues : json_encode($tickValues),
]) }} @if ($field) field="{{ $field }}" @endif>
{{ $slot }}
</template>

View File

@@ -0,0 +1,23 @@
@aware(['axis' => 'x'])
@if ($axis === 'x')
<template name="axis-line">
<line {{ $attributes->merge([
'class' => '[:where(&)]:text-zinc-300 dark:[:where(&)]:text-white/40',
'orientation' => 'bottom',
'stroke-width' => '1',
'stroke' => 'currentColor',
'fill' => 'none',
]) }}></line>
</template>
@else
<template name="axis-line">
<line {{ $attributes->merge([
'class' => '[:where(&)]:text-zinc-300 dark:[:where(&)]:text-white/40',
'orientation' => 'left',
'stroke-width' => '1',
'stroke' => 'currentColor',
'fill' => 'none',
]) }}></line>
</template>
@endif

View File

@@ -0,0 +1,31 @@
@aware(['axis' => 'x', 'position' => null])
@if ($axis === 'x')
<template name="tick-mark">
<g>
<line {{ $attributes->merge([
'class' => 'stroke-zinc-300',
'orientation' => $position === 'top' ? 'top' : 'bottom',
'stroke' => 'currentColor',
'stroke-width' => '1',
'fill' => 'none',
'y1' => '0',
'y2' => '6',
]) }}></line>
</g>
</template>
@else
<template name="tick-mark">
<g>
<line {{ $attributes->merge([
'class' => 'stroke-zinc-300',
'orientation' => $position === 'right' ? 'right' : 'left',
'stroke' => 'currentColor',
'stroke-width' => '1',
'fill' => 'none',
'x1' => $position === 'right' ? '0' : '-6',
'x2' => $position === 'right' ? '6' : '0',
]) }}></line>
</g>
</template>
@endif

View File

@@ -0,0 +1,35 @@
@aware(['axis' => 'x', 'position' => null ])
@props([
'format' => null,
])
@php
$format = is_array($format) ? \Illuminate\Support\Js::encode($format) : $format;
@endphp
@if ($axis === 'x')
<template name="tick-label" @if ($format) format="{{ $format }}" @endif>
<g>
<text {{ $attributes->merge([
'class' => '[:where(&)]:text-xs [:where(&)]:text-zinc-400 [:where(&)]:font-medium [:where(&)]:dark:text-zinc-300',
'text-anchor' => 'middle',
'fill' => 'currentColor',
'dominant-baseline' => $position === 'top' ? 'text-after-edge' : 'text-before-edge',
'dy' => $position === 'top' ? '-1em' : '1em',
]) }}><slot></slot></text>
</g>
</template>
@else
<template name="tick-label" @if ($format) format="{{ $format }}" @endif>
<g>
<text {{ $attributes->merge([
'class' => '[:where(&)]:text-xs [:where(&)]:text-zinc-400 [:where(&)]:dark:text-zinc-300',
'dominant-baseline' => 'central',
'fill' => 'currentColor',
'text-anchor' => $position === 'right' ? 'start' : 'end',
'dx' => $position === 'right' ? '1em' : '-1em',
]) }}><slot></slot></text>
</g>
</template>
@endif

View File

@@ -0,0 +1,11 @@
@blaze
<template name="cursor">
<line {{ $attributes->merge([
'class' => 'text-zinc-500 dark:text-zinc-300',
'type' => 'vertical',
'stroke' => 'currentColor',
'stroke-width' => '1',
'stroke-dasharray' => '4,4',
]) }}></line>
</template>

View File

@@ -0,0 +1,18 @@
@blaze
@props([
'tooltip' => null,
'summary' => null,
'value' => null,
'svg' => null,
])
@php
$classes = Flux::classes('block [:where(&)]:relative');
$value = is_array($value) ? \Illuminate\Support\Js::encode($value) : $value;
@endphp
<ui-chart {{ $attributes->class($classes) }} wire:ignore.children @if ($value) value="{{ $value }}" @endif>
{{ $slot }}
</ui-chart>

View File

@@ -0,0 +1,21 @@
@blaze
@props([
'label' => null,
'field' => null,
'format' => null,
])
@php
$format = is_array($format) ? \Illuminate\Support\Js::encode($format) : $format;
@endphp
<div {{ $attributes->class(['flex items-center gap-2 p-2']) }}>
{{ $slot }}
@if (is_string($label) && $label !== '')
<div class="text-xs text-zinc-500 dark:text-zinc-400">{{ $label }}</div>
@elseif ($label)
{{ $label }}
@endif
</div>

View File

@@ -0,0 +1,3 @@
@blaze
<div {{ $attributes->class('size-2.5 rounded-full') }}></div>

View File

@@ -0,0 +1,15 @@
@blaze
@props([
'field' => 'value',
])
<template name="line" field="{{ $field }}" {{ $attributes->only(['curve']) }}>
<path {{ $attributes->class('[:where(&)]:text-zinc-800')->merge([
'stroke' => 'currentColor',
'stroke-width' => '2',
'fill' => 'none',
'stroke-linecap' => 'round',
'stroke-linejoin' => 'round',
]) }}></path>
</template>

View File

@@ -0,0 +1,13 @@
@blaze
@props([
'field' => 'value',
])
<template name="point" field="{{ $field }}">
<circle {{ $attributes->class('[:where(&)]:text-zinc-800 dark:[:where(&)]:text-zinc-100 [:where(&)]:stroke-white dark:[:where(&)]:stroke-zinc-900')->merge([
'r' => '4',
'fill' => 'currentColor',
'stroke-width' => '1',
]) }}></circle>
</template>

View File

@@ -0,0 +1,7 @@
@blaze
<template name="summary">
<div {{ $attributes }}>
{{ $slot }}
</div>
</template>

View File

@@ -0,0 +1,15 @@
@blaze
@props([
'field' => null,
'format' => null,
'fallback' => null,
])
@php
$format = is_array($format) ? \Illuminate\Support\Js::encode($format) : $format;
@endphp
<span {{ $attributes }}>
<slot @if ($field) field="{{ $field }}" @endif @if ($format) format="{{ $format }}" @endif @if ($fallback) fallback="{{ $fallback }}" @endif></slot>
</span>

View File

@@ -0,0 +1,11 @@
@blaze
@props([
'gutter' => null,
])
<template name="svg" @if (isset($gutter)) gutter="{{ $gutter }}" @endif>
<svg {{ $attributes->class('absolute inset-0') }} xmlns="http://www.w3.org/2000/svg" version="1.1">
{{ $slot }}
</svg>
</template>

View File

@@ -0,0 +1,17 @@
@blaze
@props([
'field' => 'date',
'format' => null,
])
@php
$format = is_array($format) ? \Illuminate\Support\Js::encode($format) : $format;
@endphp
<div {{ $attributes->class([
'bg-zinc-50 border-b border-zinc-200 dark:bg-zinc-600 dark:border-zinc-500 flex justify-between items-center p-2',
'text-xs font-medium [:where(&)]:text-zinc-800 dark:[:where(&)]:text-zinc-100'
]) }}>
<slot field="{{ $field }}" @if ($format) format="{{ $format }}" @endif></slot>
</div>

View File

@@ -0,0 +1,19 @@
@blaze
@props([
'field' => null,
'format' => null,
])
@php
$format = is_array($format) ? \Illuminate\Support\Js::encode($format) : $format;
$classes = Flux::classes()
->add('opacity-0 data-active:opacity-100 absolute flex flex-col rounded-lg overflow-hidden shadow-lg border border-zinc-200 bg-white dark:border-zinc-500 dark:bg-zinc-700');
@endphp
<template name="tooltip">
<div {{ $attributes->class($classes) }}>
{{ $slot}}
</div>
</template>

View File

@@ -0,0 +1,27 @@
@blaze
@props([
'label' => null,
'field' => null,
'format' => null,
])
@php
$format = is_array($format) ? \Illuminate\Support\Js::encode($format) : $format;
@endphp
<div {{ $attributes->class(['flex items-center gap-2 p-2 text-xs [:where(&)]:text-zinc-500 dark:[:where(&)]:text-zinc-300']) }}>
{{ $slot }}
@if (is_string($label) && $label !== '')
<div class="text-zinc-800 dark:text-white">{{ $label }}</div>
@elseif ($label)
{{ $label }}
@endif
@if ($field)
<div class="flex-1"></div>
<slot field="{{ $field }}" @if ($format) format="{{ $format }}" @endif></slot>
@endif
</div>

View File

@@ -0,0 +1,14 @@
@blaze
@props([
'field' => null,
'format' => null,
])
@php
$format = is_array($format) ? \Illuminate\Support\Js::encode($format) : $format;
@endphp
<span {{ $attributes }}>
<slot @if ($field) field="{{ $field }}" @endif @if ($format) format="{{ $format }}" @endif></slot>
</span>

View File

@@ -0,0 +1,5 @@
@blaze
<div {{ $attributes->class('[:where(&)]:relative') }}>
{{ $slot }}
</div>

View File

@@ -0,0 +1,15 @@
@aware(['axis' => 'x'])
<template name="zero-line">
<line {{ $attributes->merge([
'class' => '[:where(&)]:text-zinc-400',
'orientation' => 'left',
'stroke-width' => '1',
'stroke' => 'currentColor',
'fill' => 'none',
'x1' => '0',
'y1' => '0',
'x2' => '0',
'y2' => '6',
]) }}></line>
</template>

View File

@@ -0,0 +1,2 @@
<flux:checkbox all :$attributes />

View File

@@ -0,0 +1,5 @@
@props([
'variant' => 'default',
])
<flux:delegate-component :component="'checkbox.group.variants.' . $variant">{{ $slot }}</flux:delegate-component>

View File

@@ -0,0 +1,25 @@
@props([
'variant' => null,
'size' => null,
'name' => null,
])
@php
// We only want to show the name attribute on the checkbox if it has been set
// manually, but not if it has been set from the wire:model attribute...
$showName = isset($name);
if (! isset($name)) {
$name = $attributes->whereStartsWith('wire:model')->first();
}
$classes = Flux::classes()
->add('flex flex-wrap gap-2')
;
@endphp
<flux:with-field :$attributes>
<ui-checkbox-group {{ $attributes->class($classes) }} @if($showName) name="{{ $name }}" @endif data-flux-checkbox-group-buttons>
{{ $slot }}
</ui-checkbox-group>
</flux:with-field>

View File

@@ -0,0 +1,25 @@
@props([
'variant' => null,
'size' => null,
'name' => null,
])
@php
// We only want to show the name attribute on the checkbox if it has been set
// manually, but not if it has been set from the wire:model attribute...
$showName = isset($name);
if (! isset($name)) {
$name = $attributes->whereStartsWith('wire:model')->first();
}
$classes = Flux::classes()
->add('flex gap-3')
;
@endphp
<flux:with-field :$attributes>
<ui-checkbox-group {{ $attributes->class($classes) }} @if($showName) name="{{ $name }}" @endif data-flux-checkbox-group-cards>
{{ $slot }}
</ui-checkbox-group>
</flux:with-field>

View File

@@ -0,0 +1,22 @@
@php
$classes = Flux::classes()
->add('*:data-flux-field:mb-3')
->add('[&>[data-flux-field]:has(>[data-flux-description])]:mb-4')
->add('[&>[data-flux-field]:last-child]:mb-0!')
;
// Support adding the .self modifier to the wire:model directive...
if (($wireModel = $attributes->wire('model')) && $wireModel->directive && ! $wireModel->hasModifier('self')) {
unset($attributes[$wireModel->directive]);
$wireModel->directive .= '.self';
$attributes = $attributes->merge([$wireModel->directive => $wireModel->value]);
}
@endphp
<flux:with-field :$attributes>
<ui-checkbox-group {{ $attributes->class($classes) }} data-flux-checkbox-group>
{{ $slot }}
</ui-checkbox-group>
</flux:with-field>

View File

@@ -0,0 +1,25 @@
@props([
'variant' => null,
'size' => null,
'name' => null,
])
@php
// We only want to show the name attribute on the checkbox if it has been set
// manually, but not if it has been set from the wire:model attribute...
$showName = isset($name);
if (! isset($name)) {
$name = $attributes->whereStartsWith('wire:model')->first();
}
$classes = Flux::classes()
->add('flex flex-wrap gap-3')
;
@endphp
<flux:with-field :$attributes>
<ui-checkbox-group {{ $attributes->class($classes) }} @if($showName) name="{{ $name }}" @endif data-flux-checkbox-group-pills>
{{ $slot }}
</ui-checkbox-group>
</flux:with-field>

View File

@@ -0,0 +1,16 @@
@blaze
@aware([ 'variant' ])
@props([
'variant' => 'default',
])
@php
// This prevents variants picked up by `@aware()` from other wrapping components like flux::modal from being used here...
$variant = $variant !== 'default' && Flux::componentExists('checkbox.variants.' . $variant)
? $variant
: 'default';
@endphp
<flux:delegate-component :component="'checkbox.variants.' . $variant">{{ $slot }}</flux:delegate-component>

View File

@@ -0,0 +1,32 @@
@blaze
@php
$classes = Flux::classes()
->add('shrink-0 size-[1.125rem] rounded-[.3rem] flex justify-center items-center')
->add('text-sm text-zinc-700 dark:text-zinc-800')
->add('shadow-xs [ui-checkbox[disabled]_&]:opacity-75 [ui-checkbox[data-checked][disabled]_&]:opacity-50 [ui-checkbox[disabled]_&]:shadow-none [ui-checkbox[data-checked]_&]:shadow-none [ui-checkbox[data-indeterminate]_&]:shadow-none')
->add('[ui-checkbox[data-checked]:not([data-indeterminate])_&>svg:first-child]:block [ui-checkbox[data-indeterminate]_&>svg:last-child]:block')
->add([
'border',
'border-zinc-300 dark:border-white/10',
'[ui-checkbox[disabled]_&]:border-zinc-200 dark:[ui-checkbox[disabled]_&]:border-white/5',
'[ui-checkbox[data-checked]_&]:border-transparent [ui-checkbox[data-indeterminate]_&]:border-transparent',
'[ui-checkbox[disabled][data-checked]_&]:border-transparent [ui-checkbox[disabled][data-indeterminate]_&]:border-transparent',
'[print-color-adjust:exact]',
])
->add([
'bg-white dark:bg-white/10',
'[ui-checkbox[data-checked]_&]:bg-[var(--color-accent)]',
'hover:[ui-checkbox[data-checked]_&]:bg-(--color-accent)',
'focus:[ui-checkbox[data-checked]_&]:bg-(--color-accent)',
'[ui-checkbox[data-indeterminate]_&]:bg-[var(--color-accent)]',
'hover:[ui-checkbox[data-indeterminate]_&]:bg-(--color-accent)',
'focus:[ui-checkbox[data-indeterminate]_&]:bg-(--color-accent)',
])
;
@endphp
<div {{ $attributes->class($classes) }} data-flux-checkbox-indicator>
<flux:icon.check variant="micro" class="hidden text-[var(--color-accent-foreground)]" />
<flux:icon.minus variant="micro" class="hidden text-[var(--color-accent-foreground)]" />
</div>

View File

@@ -0,0 +1,67 @@
@blaze
@props([
'accent' => true,
'size' => 'base',
'label' => null,
'icon' => null,
])
@php
$classes = Flux::classes()
->add('flex relative items-center font-medium justify-center gap-2 whitespace-nowrap')
->add(match ($size) {
'base' => 'h-10 text-sm rounded-lg px-4 [&:has(>:not(span):first-child)]:ps-3 [&:has(>:not(span):last-child)]:pe-3',
'sm' => 'h-8 text-sm rounded-md px-3',
'xs' => 'h-6 text-xs rounded-md px-2',
})
->add(match ($size) {
'base' => 'shadow-xs',
'sm' => 'shadow-xs',
'xs' => 'shadow-none',
})
->add('text-zinc-800 dark:text-white')
->add('bg-white dark:bg-zinc-700')
->add('after:absolute after:-inset-px after:rounded-lg')
->add('border border-zinc-200 border-b-zinc-300/80 dark:border-zinc-600')
->add([
'[--haze:color-mix(in_oklab,_var(--color-accent-content),_transparent_97.5%)]',
'[--haze-border:color-mix(in_oklab,_var(--color-accent-content),_transparent_80%)]',
'[--haze-light:color-mix(in_oklab,_var(--color-accent),_transparent_98%)]',
'dark:[--haze:color-mix(in_oklab,_var(--color-accent-content),_transparent_90%)]',
])
->add(match ($accent) {
true => [
'hover:border-[var(--haze-border)] dark:hover:border-zinc-500',
'dark:data-checked:bg-white/15 data-checked:border-(--color-accent) hover:data-checked:border-(--color-accent)',
'hover:after:bg-[var(--haze-light)] dark:hover:after:bg-white/[4%] data-checked:after:bg-(--haze) hover:data-checked:after:bg-(--haze)',
],
false => [
'hover:border-zinc-200 dark:hover:border-zinc-500',
'data-checked:bg-zinc-50 dark:data-checked:bg-white/15 data-checked:border-zinc-800 dark:data-checked:border-white',
'hover:bg-zinc-50 dark:hover:bg-zinc-600/75',
],
})
->add('disabled:opacity-75 dark:disabled:opacity-75 disabled:cursor-default disabled:pointer-events-none')
;
$iconAttributes = Flux::attributesAfter('icon:', $attributes, [
'class' => 'text-zinc-300 dark:text-zinc-400 in-data-checked:text-zinc-800 dark:in-data-checked:text-white',
'variant' => 'micro',
]);
@endphp
{{-- We have to put tabindex="-1" here because otherwise, Livewire requests will wipe out tabindex state, --}}
{{-- even with durable attributes for some reason... --}}
{{-- We have to put "data-flux-field" so that a single box can be disabled without "disabling" the group field label... --}}
<ui-checkbox {{ $attributes->class($classes) }} data-flux-control data-flux-checkbox-buttons tabindex="-1" data-flux-field>
<?php if (is_string($icon) && $icon !== ''): ?>
<flux:icon :icon="$icon" :attributes="$iconAttributes" />
<?php elseif ($icon): ?>
{{ $icon }}
<?php endif; ?>
@if (isset($label) || $slot->isNotEmpty())
<span>{{ $label ?? $slot }}</span>
@endif
</ui-checkbox>

View File

@@ -0,0 +1,78 @@
@blaze
@php $iconVariant ??= $attributes->pluck('icon:variant'); @endphp
@aware([ 'indicator' ])
@props([
'iconVariant' => 'micro',
'description' => null,
'indicator' => true,
'accent' => true,
'label' => null,
'icon' => null,
])
@php
$iconClasses = Flux::classes()
->add('inline-block mt-0.5 text-zinc-400 [ui-checkbox[data-checked]_&]:text-zinc-800 dark:[ui-checkbox[data-checked]_&]:text-white')
// When using the outline icon variant, we need to size it down to match the default icon sizes...
->add($iconVariant === 'outline' ? 'size-4' : '')
;
$classes = Flux::classes()
->add('relative flex justify-between gap-3 flex-1 p-4')
->add('rounded-lg shadow-xs')
->add('bg-white dark:bg-white/10 dark:hover:bg-white/15 dark:data-checked:bg-white/15')
->add('after:absolute after:-inset-px after:rounded-lg')
->add('border border-zinc-800/15 dark:border-white/10')
->add([
'[--haze:color-mix(in_oklab,_var(--color-accent-content),_transparent_97.5%)]',
'[--haze-border:color-mix(in_oklab,_var(--color-accent-content),_transparent_80%)]',
'[--haze-light:color-mix(in_oklab,_var(--color-accent),_transparent_98%)]',
'dark:[--haze:color-mix(in_oklab,_var(--color-accent-content),_transparent_90%)]',
])
->add(match ($accent) {
true => [
'[&:hover_[data-flux-checkbox-indicator]]:border-[var(--haze-border)] dark:[&:hover_[data-flux-checkbox-indicator]]:border-white/10',
'hover:border-[var(--haze-border)] dark:hover:border-white/10',
'data-checked:border-(--color-accent) hover:data-checked:border-(--color-accent) dark:data-checked:bg-white/15 ',
'hover:after:bg-[var(--haze-light)] dark:hover:after:bg-white/[4%] data-checked:after:bg-(--haze) hover:data-checked:after:bg-(--haze)',
],
false => [
'data-checked:bg-zinc-50 dark:data-checked:bg-white/15 data-checked:border-zinc-800 dark:data-checked:border-white',
'hover:bg-zinc-50 dark:hover:bg-white/15',
],
})
->add('[&[disabled]]:opacity-50 dark:[&[disabled]]:opacity-75 [&[disabled]]:cursor-default [&[disabled]]:pointer-events-none')
;
@endphp
{{-- We have to put tabindex="-1" here because otherwise, Livewire requests will wipe out tabindex state, --}}
{{-- even with durable attributes for some reason... --}}
{{-- We have to put "data-flux-field" so that a single box can be disabled without "disabling" the group field label... --}}
<ui-checkbox {{ $attributes->class($classes) }} data-flux-control data-flux-checkbox-cards tabindex="-1" data-flux-field>
<?php if ($label): ?>
<div class="flex-1 flex gap-2">
<?php if (is_string($icon) && $icon !== ''): ?>
<flux:icon :icon="$icon" :variant="$iconVariant" :class="$iconClasses" />
<?php elseif ($icon): ?>
{{ $icon }}
<?php endif; ?>
<div class="flex-1">
<flux:heading>{{ $label ?? $slot }}</flux:heading>
<?php if ($description): ?>
<flux:subheading size="sm">{{ $description }}</flux:subheading>
<?php endif; ?>
</div>
</div>
<flux:checkbox.indicator />
<?php else: ?>
{{ $slot }}
<?php endif; ?>
</ui-checkbox>

View File

@@ -0,0 +1,25 @@
@blaze
@props([
'name' => null,
])
@php
// We only want to show the name attribute on the checkbox if it has been set
// manually, but not if it has been set from the wire:model attribute...
$showName = isset($name);
if (! isset($name)) {
$name = $attributes->whereStartsWith('wire:model')->first();
}
$classes = Flux::classes()
->add('flex size-[1.125rem] rounded-[.3rem] mt-px outline-offset-2')
;
@endphp
<flux:with-inline-field :$attributes>
<ui-checkbox {{ $attributes->class($classes) }} @if($showName) name="{{ $name }}" @endif data-flux-control data-flux-checkbox>
<flux:checkbox.indicator />
</ui-checkbox>
</flux:with-inline-field>

View File

@@ -0,0 +1,29 @@
@blaze
@props([
'accent' => true,
'label' => null,
])
@php
$classes = Flux::classes()
->add('flex whitespace-nowrap gap-2 items-center py-1 px-2 rounded-full text-sm font-medium leading-4')
->add('bg-zinc-800/6 dark:bg-white/10 hover:bg-zinc-800/10 dark:hover:bg-white/15 text-zinc-800 hover:text-zinc-800 dark:text-white/70 dark:hover:text-white')
->add(match ($accent) {
true => 'data-checked:bg-(--color-accent) hover:data-checked:bg-(--color-accent)',
false => 'data-checked:bg-zinc-800 dark:data-checked:bg-white',
})
->add(match ($accent) {
true => 'data-checked:text-(--color-accent-foreground) hover:data-checked:text-(--color-accent-foreground)',
false => 'data-checked:text-white dark:data-checked:text-zinc-800',
})
->add('[&[disabled]]:opacity-50 dark:[&[disabled]]:opacity-75 [&[disabled]]:cursor-default [&[disabled]]:pointer-events-none')
;
@endphp
{{-- We have to put tabindex="-1" here because otherwise, Livewire requests will wipe out tabindex state, --}}
{{-- even with durable attributes for some reason... --}}
{{-- We have to put "data-flux-field" so that a single box can be disabled without "disabling" the group field label... --}}
<ui-checkbox {{ $attributes->class($classes) }} data-flux-control data-flux-checkbox-pills tabindex="-1" data-flux-field>
{{ $label ?? $slot }}
</ui-checkbox>

View File

@@ -0,0 +1,9 @@
@blaze
<ui-option-empty class="data-hidden:hidden">
<div class="flex items-center justify-center h-10">
<div class="text-sm text-zinc-500 dark:text-zinc-400 font-medium">
{{ $slot }}
</div>
</div>
</ui-option-empty>

View File

@@ -0,0 +1,16 @@
@blaze
@props([
'placeholder' => null,
])
@php
$classes = Flux::classes()
->add('w-full block rounded-xl overflow-hidden shadow-xs')
->add('border border-zinc-200 dark:border-zinc-600')
;
@endphp
<ui-select clear="action" {{ $attributes->class($classes)->merge(['filter' => true]) }} data-flux-command>
{{ $slot }}
</ui-select>

View File

@@ -0,0 +1,51 @@
@blaze
@props([
'clearable' => null,
'closable' => null,
'icon' => 'magnifying-glass',
])
@php
$classes = Flux::classes()
->add('h-12 w-full flex items-center px-3 py-2')
->add('font-medium text-base sm:text-sm text-zinc-800 dark:text-white')
->add('ps-11') // Make room for magnifying glass icon...
->add(($closable || $clearable) ? 'pe-11' : '') // Make room for close/clear button...
->add('outline-hidden')
->add('border-b border-zinc-200 dark:border-zinc-600')
->add('bg-white dark:bg-zinc-700')
// The below reverts styles added by Tailwind Forms plugin
->add('border-t-0 border-s-0 border-e-0 focus:ring-0 focus:border-zinc-200 dark:focus:border-zinc-600')
;
@endphp
<div class="relative" data-flux-command-input>
<div class="absolute top-0 bottom-0 flex items-center justify-center text-xs text-zinc-400 ps-3.5 start-0 [&:has(+input:focus)]:text-zinc-800 dark:[&:has(+input:focus)]:text-zinc-400">
<?php if (is_string($icon) && $icon !== ''): ?>
<flux:icon :$icon variant="mini" />
<?php else: ?>
{{ $icon }}
<?php endif; ?>
</div>
<input type="text" {{ $attributes->class($classes) }} />
<?php if ($closable): ?>
<div class="absolute top-0 bottom-0 flex items-center justify-center pe-2 end-0">
<ui-close>
<flux:button square variant="subtle" size="sm" aria-label="Close command modal">
<flux:icon.x-mark variant="micro" />
</flux:button>
</ui-close>
</div>
<?php elseif ($clearable): ?>
<div class="absolute top-0 bottom-0 flex items-center justify-center pe-2 end-0 [[data-flux-command-input]:has(input:placeholder-shown)_&]:hidden">
<flux:button square variant="subtle" size="sm" tabindex="-1" aria-label="Clear command input"
x-on:click="$el.closest('[data-flux-command-input]').querySelector('input').value = ''; $el.closest('[data-flux-command-input]').querySelector('input').dispatchEvent(new Event('input', { bubbles: false })); $el.closest('[data-flux-command-input]').querySelector('input').focus()"
>
<flux:icon.x-mark variant="micro" />
</flux:button>
</div>
<?php endif; ?>
</div>

View File

@@ -0,0 +1,38 @@
@blaze
@php $iconVariant ??= $attributes->pluck('icon:variant'); @endphp
@props([
'iconVariant' => 'outline',
'icon' => null,
'kbd' => null,
])
@php
$classes = Flux::classes()
->add('w-full group/item data-hidden:hidden h-10 flex items-center px-2 py-1.5 focus:outline-hidden')
->add('rounded-md')
->add('text-start text-sm font-medium')
->add('text-zinc-800 data-active:bg-zinc-100 dark:text-white dark:data-active:bg-zinc-600')
;
@endphp
<ui-option action {{ $attributes->class($classes) }} data-flux-command-item>
<?php if ($icon): ?>
<div class="relative">
<?php if (is_string($icon) && $icon !== ''): ?>
<flux:icon :$icon :variant="$iconVariant" class="me-2 size-6 text-zinc-400 dark:text-zinc-400 group-data-active/item:text-zinc-800 dark:group-data-active/item:text-white" />
<?php else: ?>
{{ $icon }}
<?php endif; ?>
</div>
<?php endif; ?>
{{ $slot }}
<?php if ($kbd): ?>
<div class="inline-flex ms-auto rounded-sm bg-zinc-800/5 dark:bg-white/10 px-1 py-0.5">
<span class="font-medium text-xs text-zinc-500 dark:text-zinc-300">{{ $kbd }}</span>
</div>
<?php endif; ?>
</ui-option>

View File

@@ -0,0 +1,15 @@
@blaze
@php
$classes = Flux::classes()
->add('p-[.3125rem]')
->add('overflow-y-auto')
->add('bg-white dark:bg-zinc-700')
;
@endphp
<ui-options {{ $attributes->class($classes) }} data-flux-command-items>
{{ $slot }}
<flux:command.empty>{!! __('No results found') !!}</flux:command.empty>
</ui-options>

View File

@@ -0,0 +1,77 @@
@props([
'name' => $attributes->whereStartsWith('wire:model')->first(),
'actionsTrailing' => null,
'actionsLeading' => null,
'variant' => null,
'invalid' => null,
'footer' => null,
'header' => null,
'input' => null,
])
@php
$invalid ??= ($name && $errors->has($name));
$classes = Flux::classes()
->add('w-full p-2')
->add('grid grid-cols-[auto_1fr_1fr_auto]')
->add('shadow-xs [&:has([disabled])]:shadow-none border')
->add('bg-white dark:bg-white/10 dark:[&:has([disabled])]:bg-white/[7%]')
->add(match ($variant) {
'input' => 'rounded-lg',
default => 'rounded-2xl [&_[data-flux-button]]:rounded-lg',
})
->add($invalid ? 'border-red-500' : 'border-zinc-200 border-b-zinc-300/80 dark:border-white/10')
;
$textareaClasses = Flux::classes()
->add('block w-full resize-none px-2 py-1.5')
->add('outline-none!')
->add('text-base sm:text-sm text-zinc-700 [[disabled]_&]:text-zinc-500 placeholder-zinc-400 [[disabled]_&]:placeholder-zinc-400/70 dark:text-zinc-300 dark:[[disabled]_&]:text-zinc-400 dark:placeholder-zinc-400 dark:[[disabled]_&]:placeholder-zinc-500')
;
// Support adding the .self modifier to the wire:model directive...
if (($wireModel = $attributes->wire('model')) && $wireModel->directive && ! $wireModel->hasModifier('self')) {
unset($attributes[$wireModel->directive]);
$wireModel->directive .= '.self';
$attributes = $attributes->merge([$wireModel->directive => $wireModel->value]);
}
@endphp
<flux:with-field :$attributes :$name>
<ui-composer {{ $attributes->class($classes) }} data-flux-composer>
<?php if ($header): ?>
<div {{ $header->attributes->class('col-span-3 flex items-center gap-1 mb-2') }}>
{{ $header }}
</div>
<?php endif; ?>
<div class="col-span-4 [[inline]_&]:col-span-2 [[inline]_&]:col-start-2">
<?php if ($input): ?>
{{ $input }}
<?php else: ?>
<textarea class="{{ $textareaClasses }}"></textarea>
<?php endif; ?>
</div>
<?php if ($actionsLeading): ?>
<div {{ $actionsLeading->attributes->class('col-span-2 [[inline]_&]:col-span-1 [[inline]_&]:col-start-1 [[inline]_&]:row-start-1 flex items-start gap-1') }}>
{{ $actionsLeading }}
</div>
<?php endif; ?>
<?php if ($actionsTrailing): ?>
<div {{ $actionsTrailing->attributes->class('col-span-2 [[inline]_&]:col-span-1 flex items-start justify-end gap-1') }}>
{{ $actionsTrailing }}
</div>
<?php endif; ?>
<?php if ($footer): ?>
<div {{ $footer->attributes->class('col-span-4 flex items-center gap-1') }}>
{{ $footer }}
</div>
<?php endif; ?>
</ui-composer>
</flux:with-field>

View File

@@ -0,0 +1,11 @@
@blaze
@php
$classes = Flux::classes()
->add('mx-auto w-full [:where(&)]:max-w-7xl px-6 lg:px-8')
;
@endphp
<div {{ $attributes->class($classes) }} data-flux-container>
{{ $slot }}
</div>

View File

@@ -0,0 +1,20 @@
@blaze
@props([
'position' => 'bottom end',
])
@php
// Support adding the .self modifier to the wire:model directive...
if (($wireModel = $attributes->wire('model')) && $wireModel->directive && ! $wireModel->hasModifier('self')) {
unset($attributes[$wireModel->directive]);
$wireModel->directive .= '.self';
$attributes = $attributes->merge([$wireModel->directive => $wireModel->value]);
}
@endphp
<ui-context position="{{ $position }}" {{ $attributes }} data-flux-context>
{{ $slot }}
</ui-context>

View File

@@ -0,0 +1,56 @@
@aware([ 'placeholder' ])
@props([
'placeholder' => null,
'clearable' => null,
'invalid' => false,
'size' => null,
])
@php
$classes = Flux::classes()
->add('group/select-button cursor-default py-2')
->add('overflow-hidden') // Overflow hidden is here to prevent the button from growing if the selected date text is too long.
->add('flex items-center')
->add('shadow-xs')
->add('bg-white dark:bg-white/10 dark:disabled:bg-white/[7%]')
// Make the placeholder match the text color of standard input placeholders...
->add('disabled:shadow-none')
->add(match ($size) {
default => 'h-10 text-base sm:text-sm rounded-lg px-3 block w-full',
'sm' => 'h-8 text-sm rounded-lg ps-3 pe-2 block w-full',
'xs' => 'h-6 text-xs rounded-lg ps-3 pe-2 block w-full',
})
->add($invalid
? 'border border-red-500'
: 'border border-zinc-200 border-b-zinc-300/80 dark:border-white/10'
)
;
@endphp
<button type="button" {{ $attributes->class($classes) }} @if ($invalid) data-invalid @endif data-flux-group-target data-flux-date-picker-button>
<flux:icon.calendar variant="mini" class="me-2 text-zinc-400/75 [[disabled]_&]:text-zinc-200! dark:text-white/60 dark:[[disabled]_&]:text-white/40!" />
<?php if ($slot->isNotEmpty()): ?>
{{ $slot }}
<?php else: ?>
<flux:date-picker.selected :$placeholder />
<?php endif; ?>
<?php if ($clearable): ?>
<flux:button as="div"
class="cursor-pointer ms-2 {{ $size === 'sm' || $size === 'xs' ? '-me-1' : '-me-2' }} [[data-flux-date-picker-button]:has([data-flux-date-picker-placeholder])_&]:hidden [[data-flux-select]:has([disabled])_&]:hidden"
variant="subtle"
:size="$size === 'sm' || $size === 'xs' ? 'xs' : 'sm'"
square
tabindex="-1"
aria-label="Clear date"
x-on:click.prevent.stop="let datePicker = $el.closest('ui-date-picker'); datePicker.clear();"
>
<flux:icon.x-mark variant="micro" />
</flux:button>
<?php endif; ?>
<flux:icon.chevron-down variant="mini" class="ms-2 -me-1 text-zinc-400/75 [[data-flux-date-picker-button]:hover_&]:text-zinc-800 [[disabled]_&]:text-zinc-200! dark:text-white/60 dark:[[data-flux-date-picker-button]:hover_&]:text-white dark:[[disabled]_&]:text-white/40!" />
</button>

View File

@@ -0,0 +1,297 @@
@props([
'selectableHeader' => null,
'withConfirmation' => null,
'weekNumbers' => null,
'placeholder' => null,
'withPresets' => null,
'unavailable' => null,
'withInputs' => null,
'clearable' => null,
'withToday' => null,
'type' => 'button',
'presets' => null,
'trigger' => null,
'invalid' => null,
'months' => null,
'value' => null,
'size' => null,
'name' => null,
'mode' => null,
])
@php
// We only want to show the name attribute if it has been set manually
// but not if it has been set from the `wire:model` attribute...
$showName = isset($name);
if (! isset($name)) {
$name = $attributes->whereStartsWith('wire:model')->first();
}
// Support adding the .self modifier to the wire:model directive...
if (($wireModel = $attributes->wire('model')) && $wireModel->directive && ! $wireModel->hasModifier('self')) {
unset($attributes[$wireModel->directive]);
$wireModel->directive .= '.self';
$attributes = $attributes->merge([$wireModel->directive => $wireModel->value]);
}
$months = $months ?? ($mode === 'range' ? 2 : 1);
$range = $mode === 'range';
$placeholder = $placeholder ?? ($range ? __('Select a date range') : __('Select a date'));
// Mark it invalid if the property or any of it's nested attributes have errors...
$invalid ??= ($name && ($errors->has($name) || $errors->has($name . '.*')));
$class = Flux::classes()
->add('block min-w-0')
// The below reverts styles added by Tailwind Forms plugin...
->add('border-0 p-0 bg-transparent')
;
$sizeClasses = match ($size) {
'2xl' => $weekNumbers ? 'size-11 sm:size-14' : 'size-12 sm:size-12',
'xl' => $weekNumbers ? 'size-11 sm:size-12' : 'size-12 sm:size-12',
'lg' => $weekNumbers ? 'size-10 sm:size-11' : 'size-11 sm:size-11',
default => $weekNumbers ? 'size-10 sm:size-10' : 'size-11 sm:size-10',
'sm' => $weekNumbers ? 'size-10 sm:size-9' : 'size-11 sm:size-9',
};
if ($withPresets) {
$presets = $presets ?? 'today yesterday thisWeek last7Days thisMonth yearToDate allTime';
}
$presetArrayOfStrings = (string) is_string($presets) ? explode(' ', $presets) : [];
$presetArray = array_map(function ($preset) {
return Flux\DateRangePreset::from($preset);
}, $presetArrayOfStrings);
// Add support for `$value` being an array, if for example it's coming from
// the `old()` helper or if a user prefers to pass data in as an array...
if (is_array($value)) {
$value = match (true) {
$mode === 'range' => isset($value['start']) && isset($value['end']) ? $value['start'] . '/' . $value['end'] : null,
default => collect($value)->join(','),
};
}
if (isset($unavailable)) {
$unavailable = collect($unavailable)->implode(',');
}
@endphp
<flux:with-field :$attributes :$name>
<ui-date-picker
{{ $attributes->class($class) }}
data-flux-control
data-flux-date-picker
@if ($mode) mode="{{ $mode }}" @endif
months="1"
sm:months="{{ $months }}"
@if (isset($unavailable) && $unavailable !== '') unavailable="{{ $unavailable }}" @endif
@if ($showName) name="{{ $name }}" @endif
@if (isset($value)) value="{{ $value }}" @endif
>
<?php if ($trigger === null): ?>
<?php if ($type === 'input'): ?>
<flux:date-picker.input :$placeholder :$invalid :$size :$clearable />
<?php else: ?>
<flux:date-picker.button :$placeholder :$invalid :$size :$clearable />
<?php endif; ?>
<?php else: ?>
{{ $trigger }}
<?php endif; ?>
<dialog wire:ignore class="max-sm:max-h-full! rounded-xl shadow-xl sm:shadow-2xs max-sm:fixed! max-sm:inset-0! sm:backdrop:bg-transparent bg-white dark:bg-zinc-900 sm:border border-zinc-200 dark:border-white/10">
<ui-calendar class="isolate relative grid sm:grid-cols-[auto_1fr] grid-rows-[auto_auto_auto_auto_auto]" wire:ignore>
<?php if ($presets): ?>
<ui-calendar-presets class="row-span-full max-sm:hidden border-e border-zinc-200 dark:border-zinc-600">
<ui-radio-group class="flex flex-col gap-1 p-2 min-w-[120px]">
@foreach ($presetArray as $preset)
<ui-radio
value="{{ $preset->value }}"
class="text-sm font-medium text-zinc-600 dark:text-zinc-300 data-checked:bg-(--color-accent) data-checked:text-(--color-accent-foreground) px-2 py-1.5 whitespace-nowrap rounded-lg hover:bg-zinc-100 hover:text-zinc-800 dark:hover:bg-white/5 dark:hover:text-white"
>{{ $preset->label() }}</ui-radio>
@endforeach
</ui-radio-group>
</ui-calendar-presets>
<?php else: ?>
<div class="row-span-full"></div>
<?php endif; ?>
<?php if ($withInputs): ?>
<ui-calendar-inputs class="flex items-center p-2 border-b border-zinc-200 dark:border-white/10">
<?php if ($range): ?>
<div class="sm:px-2 flex items-center gap-4">
<div class="flex items-center gap-2"><span class="max-sm:hidden text-sm font-medium text-zinc-800 dark:text-white">{{ __('Start') }}</span> <flux:input type="date" class="w-[full] sm:w-[11.25rem]" /></div>
<div class="flex items-center gap-2"><span class="max-sm:hidden text-sm font-medium text-zinc-800 dark:text-white">{{ __('End') }}</span> <flux:input type="date" class="w-[full] sm:w-[11.25rem]" /></div>
</div>
<?php else: ?>
<flux:input type="date" class="w-full sm:w-[11.25rem]" />
<?php endif; ?>
</ui-calendar-inputs>
<?php endif; ?>
<div class="relative">
<div class="z-10 absolute top-0 inset-x-0 p-2">
<header class="flex justify-between items-center">
<div class="flex items-center gap-2">
<?php if ($selectableHeader): ?>
<ui-calendar-month display="short" class="font-medium text-sm text-zinc-800 dark:text-white">
<select
class="h-10 py-0 border-0 text-sm sm:h-8 appearance-none rounded-lg bg-zinc-100 dark:bg-white/10 dark:[&>option]:bg-zinc-700 dark:[&>option]:text-white px-3 sm:ps-2 [background-position:_right_.25rem_center_!important] rtl:[background-position:_left_.25rem_center_!important] pe-[1.35rem] bg-[length:16px_16px] bg-[url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2016%2016%22%20fill=%22%2300000040%22%20class=%22size-4%22%3E%3Cpath%20fill-rule=%22evenodd%22%20d=%22M4.22%206.22a.75.75%200%200%201%201.06%200L8%208.94l2.72-2.72a.75.75%200%201%201%201.06%201.06l-3.25%203.25a.75.75%200%200%201-1.06%200L4.22%207.28a.75.75%200%200%201%200-1.06Z%22%20clip-rule=%22evenodd%22/%3E%3C/svg%3E')] hover:bg-[length:16px_16px] hover:bg-[url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2016%2016%22%20fill=%22%231f2937%22%20class=%22size-4%22%3E%3Cpath%20fill-rule=%22evenodd%22%20d=%22M4.22%206.22a.75.75%200%200%201%201.06%200L8%208.94l2.72-2.72a.75.75%200%201%201%201.06%201.06l-3.25%203.25a.75.75%200%200%201-1.06%200L4.22%207.28a.75.75%200%200%201%200-1.06Z%22%20clip-rule=%22evenodd%22/%3E%3C/svg%3E')] dark:bg-[length:16px_16px] dark:bg-[url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2016%2016%22%20fill=%22%23ffffff75%22%20class=%22size-4%22%3E%3Cpath%20fill-rule=%22evenodd%22%20d=%22M4.22%206.22a.75.75%200%200%201%201.06%200L8%208.94l2.72-2.72a.75.75%200%201%201%201.06%201.06l-3.25%203.25a.75.75%200%200%201-1.06%200L4.22%207.28a.75.75%200%200%201%200-1.06Z%22%20clip-rule=%22evenodd%22/%3E%3C/svg%3E')] dark:hover:bg-[length:16px_16px] dark:hover:bg-[url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2016%2016%22%20fill=%22%23ffffff%22%20class=%22size-4%22%3E%3Cpath%20fill-rule=%22evenodd%22%20d=%22M4.22%206.22a.75.75%200%200%201%201.06%200L8%208.94l2.72-2.72a.75.75%200%201%201%201.06%201.06l-3.25%203.25a.75.75%200%200%201-1.06%200L4.22%207.28a.75.75%200%200%201%200-1.06Z%22%20clip-rule=%22evenodd%22/%3E%3C/svg%3E')] bg-no-repeat"
>
<template>
<option><slot></slot></option>
</template>
</select>
</ui-calendar-month>
<ui-calendar-year class="font-medium text-sm text-zinc-800 dark:text-white">
<select
class="h-10 py-0 border-0 text-sm sm:h-8 appearance-none rounded-lg bg-zinc-100 dark:bg-white/10 dark:[&>option]:bg-zinc-700 dark:[&>option]:text-white px-3 sm:ps-2 [background-position:_right_.25rem_center_!important] rtl:[background-position:_left_.25rem_center_!important] pe-[1.35rem] bg-[length:16px_16px] bg-[url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2016%2016%22%20fill=%22%2300000040%22%20class=%22size-4%22%3E%3Cpath%20fill-rule=%22evenodd%22%20d=%22M4.22%206.22a.75.75%200%200%201%201.06%200L8%208.94l2.72-2.72a.75.75%200%201%201%201.06%201.06l-3.25%203.25a.75.75%200%200%201-1.06%200L4.22%207.28a.75.75%200%200%201%200-1.06Z%22%20clip-rule=%22evenodd%22/%3E%3C/svg%3E')] hover:bg-[length:16px_16px] hover:bg-[url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2016%2016%22%20fill=%22%231f2937%22%20class=%22size-4%22%3E%3Cpath%20fill-rule=%22evenodd%22%20d=%22M4.22%206.22a.75.75%200%200%201%201.06%200L8%208.94l2.72-2.72a.75.75%200%201%201%201.06%201.06l-3.25%203.25a.75.75%200%200%201-1.06%200L4.22%207.28a.75.75%200%200%201%200-1.06Z%22%20clip-rule=%22evenodd%22/%3E%3C/svg%3E')] dark:bg-[length:16px_16px] dark:bg-[url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2016%2016%22%20fill=%22%23ffffff75%22%20class=%22size-4%22%3E%3Cpath%20fill-rule=%22evenodd%22%20d=%22M4.22%206.22a.75.75%200%200%201%201.06%200L8%208.94l2.72-2.72a.75.75%200%201%201%201.06%201.06l-3.25%203.25a.75.75%200%200%201-1.06%200L4.22%207.28a.75.75%200%200%201%200-1.06Z%22%20clip-rule=%22evenodd%22/%3E%3C/svg%3E')] dark:hover:bg-[length:16px_16px] dark:hover:bg-[url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2016%2016%22%20fill=%22%23ffffff%22%20class=%22size-4%22%3E%3Cpath%20fill-rule=%22evenodd%22%20d=%22M4.22%206.22a.75.75%200%200%201%201.06%200L8%208.94l2.72-2.72a.75.75%200%201%201%201.06%201.06l-3.25%203.25a.75.75%200%200%201-1.06%200L4.22%207.28a.75.75%200%200%201%200-1.06Z%22%20clip-rule=%22evenodd%22/%3E%3C/svg%3E')] bg-no-repeat"
>
<template>
<option><slot></slot></option>
</template>
</select>
</ui-calendar-year>
<?php endif; ?>
</div>
<div class="flex items-center">
<?php if ($withToday): ?>
<ui-calendar-today class="size-10 sm:size-8 rounded-lg flex items-center justify-center text-zinc-400 hover:bg-zinc-100 hover:text-zinc-800 dark:hover:bg-white/5 dark:hover:text-white [&[disabled]]:opacity-50 [&[disabled]]:pointer-events-none" aria-label="Previous month">
<div class="relative">
<template name="today">
<div class="cursor-default absolute inset-0 mt-[3px] flex items-center justify-center text-[.5625rem] font-semibold"><slot></slot></div>
</template>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.75 2C5.94891 2 6.13968 2.07902 6.28033 2.21967C6.42098 2.36032 6.5 2.55109 6.5 2.75V4H13.5V2.75C13.5 2.55109 13.579 2.36032 13.7197 2.21967C13.8603 2.07902 14.0511 2 14.25 2C14.4489 2 14.6397 2.07902 14.7803 2.21967C14.921 2.36032 15 2.55109 15 2.75V4H15.25C15.9793 4 16.6788 4.28973 17.1945 4.80546C17.7103 5.32118 18 6.02065 18 6.75V15.25C18 15.9793 17.7103 16.6788 17.1945 17.1945C16.6788 17.7103 15.9793 18 15.25 18H4.75C4.02065 18 3.32118 17.7103 2.80546 17.1945C2.28973 16.6788 2 15.9793 2 15.25V6.75C2 6.02065 2.28973 5.32118 2.80546 4.80546C3.32118 4.28973 4.02065 4 4.75 4H5V2.75C5 2.55109 5.07902 2.36032 5.21967 2.21967C5.36032 2.07902 5.55109 2 5.75 2ZM4.75 6.5C4.06 6.5 3.5 7.06 3.5 7.75V15.25C3.5 15.94 4.06 16.5 4.75 16.5H15.25C15.94 16.5 16.5 15.94 16.5 15.25V7.75C16.5 7.06 15.94 6.5 15.25 6.5H4.75Z" fill="currentColor"/>
</svg>
</div>
</ui-calendar-today>
<?php endif; ?>
<ui-calendar-previous class="size-10 sm:size-8 rounded-lg flex items-center justify-center text-zinc-400 hover:bg-zinc-100 hover:text-zinc-800 dark:hover:bg-white/5 dark:hover:text-white [&[disabled]]:opacity-50 [&[disabled]]:pointer-events-none" aria-label="Previous month">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5 rtl:hidden"> <path fill-rule="evenodd" d="M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z" clip-rule="evenodd" /> </svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5 hidden rtl:block"> <path fill-rule="evenodd" d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" /> </svg>
</ui-calendar-previous>
<ui-calendar-next class="size-10 sm:size-8 rounded-lg flex items-center justify-center text-zinc-400 hover:bg-zinc-100 hover:text-zinc-800 dark:hover:bg-white/5 dark:hover:text-white [&[disabled]]:opacity-50 [&[disabled]]:pointer-events-none [&[disabled]_&]:text-zinc-400" aria-label="Next month">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5 rtl:hidden"> <path fill-rule="evenodd" d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" /> </svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5 hidden rtl:block"> <path fill-rule="evenodd" d="M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z" clip-rule="evenodd" /> </svg>
</ui-calendar-next>
</div>
</header>
</div>
</div>
<ui-calendar-months class="relative flex justify-center p-2 gap-4">
<template name="month">
<div>
<template name="heading">
<div class="@if ($selectableHeader) [[data-month]:first-of-type_&]:opacity-0 @endif mb-2 px-2 h-10 sm:h-8 flex items-center">
<div class="font-medium text-sm text-zinc-800 dark:text-white"><slot></slot></div>
</div>
</template>
<table>
<thead>
<tr class="flex w-full">
<?php if ($weekNumbers): ?>
<th scope="col" class="{{ $sizeClasses }} text-sm font-medium text-zinc-500 dark:text-zinc-300 flex items-center"><div class="w-full">#</div></th>
<?php endif; ?>
<template name="weekday">
<th scope="col" class="{{ $sizeClasses }} text-sm font-medium text-zinc-500 dark:text-zinc-300 flex items-center"><div class="w-full"><slot></slot></div></th>
</template>
</tr>
</thead>
<tbody>
<template name="week">
<tr class="flex w-full not-first-of-type:mt-1 [&:first-of-type_td[data-in-range]:not([data-selected]):first-child]:rounded-s-none [&:last-of-type_td[data-in-range]:not([data-selected]):last-child]:rounded-e-none">
<?php if ($weekNumbers): ?>
<template name="number">
<td class="p-0 relative {{ $sizeClasses }} text-xs font-medium text-zinc-400 flex items-center justify-center">
<slot></slot>
</td>
</template>
<?php endif; ?>
<template name="day">
<?php if ($attributes->has('static')): ?>
<td class="p-0 data-unavailable:line-through data-in-range:bg-zinc-100 dark:data-in-range:bg-white/10 data-start:rounded-s-lg data-end:rounded-e-lg data-end-preview:rounded-e-lg first-of-type:rounded-s-lg last-of-type:rounded-e-lg [&[data-selected]+[data-selected]]:rounded-s-none">
<div class="relative {{ $sizeClasses }} text-sm font-medium text-zinc-800 dark:text-white flex items-center justify-center rounded-lg [td[data-selected]:has(+td[data-selected])_&]:rounded-e-none [td[data-selected]+td[data-selected]_&]:rounded-s-none [td[data-selected]_&]:bg-[var(--color-accent)] [td[data-selected]_&]:text-[var(--color-accent-foreground)] [td[data-selected]_&[disabled]]:opacity-50 disabled:text-zinc-400 disabled:pointer-events-none disabled:cursor-default">
<div class="absolute inset-0 hidden [td[data-today]_&]:flex justify-center items-end"><div class="mb-1 size-1 rounded-full bg-zinc-800 dark:bg-white [td[data-selected]_&]:bg-white dark:[td[data-selected]_&]:bg-zinc-800"></div></div>
<slot></slot>
</div>
</td>
<?php else: ?>
<td class="_max-sm:data-outside:opacity-0 p-0 data-unavailable:line-through data-in-range:bg-zinc-100 dark:data-in-range:bg-white/10 data-start:rounded-s-lg data-end:rounded-e-lg data-end-preview:rounded-e-lg first-of-type:rounded-s-lg last-of-type:rounded-e-lg [&[data-selected]+[data-selected]]:rounded-s-none [[data-in-range]:not([data-selected]):not([data-end-preview])+&[data-outside]]:bg-linear-to-r [&[data-outside]:has(+[data-in-range])]:bg-linear-to-l data-outside:opacity-50 from-zinc-100 dark:from-white/10 from-1% [&[data-outside]:has(+[data-in-range][data-selected])]:bg-none!">
<ui-tooltip position="top">
<button type="button" class="{{ $sizeClasses }} text-sm font-medium text-zinc-800 dark:text-white flex flex-col items-center justify-center rounded-lg hover:bg-zinc-800/5 dark:hover:bg-white/5 [td[data-selected]:has(+td[data-selected])_&]:rounded-e-none [td[data-selected]+td[data-selected]_&]:rounded-s-none [td[data-selected]_&]:bg-[var(--color-accent)] [td[data-selected]_&]:text-[var(--color-accent-foreground)] [td[data-selected]_&[disabled]]:opacity-50 disabled:text-zinc-400 disabled:pointer-events-none disabled:cursor-default [[readonly]_&]:pointer-events-none [[readonly]_&]:cursor-default [[readonly]_&]:bg-transparent">
<div class="relative">
<div class="absolute inset-x-0 bottom-[-3px] hidden [td[data-today]_&]:flex justify-center items-end"><div class="size-1 rounded-full bg-zinc-800 dark:bg-white [td[data-selected]_&]:bg-white dark:[td[data-selected]_&]:bg-zinc-800"></div></div>
<div><slot></slot></div>
<template name="subtext">
<div class="absolute inset-x-0 bottom-[-1rem] flex justify-center font-medium text-xs text-zinc-400 dark:text-zinc-500 [[data-date-variant='success']_&]:text-lime-600 dark:[[data-date-variant='success']_&]:text-lime-400 [[data-date-variant='warning']_&]:text-yellow-600 dark:[[data-date-variant='warning']_&]:text-yellow-400 [[data-date-variant='danger']_&]:text-rose-500 dark:[[data-date-variant='danger']_&]:text-rose-400">
<slot></slot>
</div>
</template>
</div>
</button>
<template name="details">
<div popover="manual" class="relative py-2 px-2.5 rounded-md text-xs text-white font-medium bg-zinc-800 dark:bg-zinc-700 dark:border dark:border-white/10 p-0 overflow-visible">
<slot></slot>
</div>
</template>
</ui-tooltip>
</td>
<?php endif; ?>
</template>
</tr>
</template>
</tbody>
</table>
</div>
</template>
</ui-calendar-months>
<?php if ($presets): ?>
<ui-calendar-presets class="block sm:hidden px-4">
<select class="appearance-none w-full ps-3 pe-10 block h-10 py-2 text-sm rounded-lg shadow-2xs border bg-white dark:bg-white/10 dark:disabled:bg-white/[9%] text-zinc-700 dark:text-zinc-300 has-[option.placeholder:checked]:text-zinc-400 dark:has-[option.placeholder:checked]:text-zinc-400 disabled:shadow-none border border-zinc-200 border-b-zinc-300/80 dark:border-white/10" data-flux-control="" data-flux-select-native="" data-flux-group-target="">
<option value="" disabled="" selected="" class="placeholder">{{ __('Choose predefined range...') }}</option>
@foreach ($presetArray as $preset)
<option value="{{ $preset->value }}">{{ $preset->label() }}</option>
@endforeach
</select>
</ui-calendar-presets>
<?php endif; ?>
<div class="@unless ($withConfirmation) sm:hidden @endunless p-4 sm:p-2 flex justify-end gap-2">
<ui-close>
<flux:button variant="ghost">{{ __('Cancel') }}</flux:button>
</ui-close>
<ui-date-picker-select>
<flux:button variant="primary">
<?php if ($range): ?>
{{ __('Select range') }}
<?php else: ?>
{{ __('Select date') }}
<?php endif; ?>
</flux:button>
</ui-date-picker-select>
</div>
</ui-calendar>
</dialog>
</ui-date-picker>
</flux:with-field>

View File

@@ -0,0 +1,27 @@
@aware([ 'placeholder' ])
@props([
'placeholder' => null,
'clearable' => false,
'invalid' => null,
'size' => null,
])
{{-- For Firefox, we need to reset the inputs padding back to the default as if there is no trailing icon, so the native date input calendar icon is correctly positioned... --}}
<flux:input type="date" :$invalid :$size :$placeholder :$attributes class:input="[@supports(-moz-appearance:none)]:pe-3">
<x-slot name="iconTrailing">
<?php if ($clearable): ?>
<div class="absolute top-0 bottom-0 flex items-center justify-center pe-10 end-0">
<flux:input.clearable :$size as="div" />
</div>
<?php endif; ?>
{{-- Hide this button on Firefox because we can't get rid of the default date input calendar icon, so hide ours instead... --}}
<flux:button size="sm" square variant="subtle" class="-me-2 [@supports(-moz-appearance:none)]:hidden [[disabled]_&]:pointer-events-none [&:hover>*]:text-zinc-800 dark:[&:hover>*]:text-white">
<flux:icon.calendar variant="mini" class="text-zinc-300 [[disabled]_&]:text-zinc-200! dark:text-white/60 dark:[[disabled]_&]:text-white/40!" />
</flux:button>
{{-- Show this button only on Firefox as it's a clickable overlay that sits over the top of the default date input calendar icon to display our date picker... --}}
<flux:button size="sm" square variant="subtle" class="not-[@supports(-moz-appearance:none)]:hidden absolute! w-6! h-auto! right-3.5 top-2 bottom-2 sm:w-6! sm:right-3 sm:top-2 sm:bottom-2" />
</x-slot>
</flux:input>

View File

@@ -0,0 +1,17 @@
@blaze
@props([
'placeholder' => null,
])
<ui-selected-date x-ignore wire:ignore class="truncate flex gap-2 text-start flex-1 text-zinc-700 [[disabled]_&]:text-zinc-500 dark:text-zinc-300 dark:[[disabled]_&]:text-zinc-400">
<template name="placeholder">
<span class="text-zinc-400 [[disabled]_&]:text-zinc-400/70 dark:text-zinc-400 dark:[[disabled]_&]:text-zinc-500" data-flux-date-picker-placeholder>
{{ $placeholder ?? new Illuminate\Support\HtmlString('<slot></slot>') }}
</span>
</template>
<template name="date">
<div dir="auto"><slot></slot></div>
</template>
</ui-selected-date>

View File

@@ -0,0 +1,18 @@
@blaze
@php $srOnly = $srOnly ??= $attributes->pluck('sr-only'); @endphp
@props([
'srOnly' => null,
])
@php
$classes = Flux::classes()
->add('text-sm text-zinc-500 dark:text-white/60')
->add($srOnly ? 'sr-only' : '')
;
@endphp
<ui-description {{ $attributes->class($classes) }} data-flux-description>
{{ $slot }}
</ui-description>

View File

@@ -0,0 +1,21 @@
@blaze
@props([
'position' => 'bottom',
'align' => 'start',
])
@php
// Support adding the .self modifier to the wire:model directive...
if (($wireModel = $attributes->wire('model')) && $wireModel->directive && ! $wireModel->hasModifier('self')) {
unset($attributes[$wireModel->directive]);
$wireModel->directive .= '.self';
$attributes = $attributes->merge([$wireModel->directive => $wireModel->value]);
}
@endphp
<ui-dropdown position="{{ $position }} {{ $align }}" {{ $attributes }} data-flux-dropdown>
{{ $slot }}
</ui-dropdown>

View File

@@ -0,0 +1,35 @@
@blaze
@props([
'kbd' => null,
])
<ui-select data-editor="align" class="relative">
<flux:tooltip content="{{ __('Align') }}" :$kbd class="contents">
<flux:editor.button>
<ui-selected>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5 shrink-0" aria-hidden="true"> <path fill-rule="evenodd" d="M2 4.75A.75.75 0 0 1 2.75 4h14.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 4.75Zm0 10.5a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5a.75.75 0 0 1-.75-.75ZM2 10a.75.75 0 0 1 .75-.75h14.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 10Z" clip-rule="evenodd" /> </svg>
<div class="[ui-selected_&]:sr-only">{{ __('Left') }}</div>
</ui-selected>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4" aria-hidden="true"> <path fill-rule="evenodd" d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" /> </svg>
</flux:editor.button>
</flux:tooltip>
<ui-options popover="manual" class="min-w-[10rem] rounded-lg border border-zinc-200 dark:border-zinc-600 bg-white dark:bg-zinc-700 shadow-xs p-[5px]">
<flux:editor.option value="left" selected>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5 shrink-0" aria-hidden="true"> <path fill-rule="evenodd" d="M2 4.75A.75.75 0 0 1 2.75 4h14.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 4.75Zm0 10.5a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5a.75.75 0 0 1-.75-.75ZM2 10a.75.75 0 0 1 .75-.75h14.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 10Z" clip-rule="evenodd" /> </svg>
<div class="[ui-selected_&]:sr-only">{{ __('Left') }}</div>
</flux:editor.option>
<flux:editor.option value="center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5 shrink-0" aria-hidden="true"> <path fill-rule="evenodd" d="M2 4.75A.75.75 0 0 1 2.75 4h14.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 4.75ZM2 10a.75.75 0 0 1 .75-.75h14.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 10Zm0 5.25a.75.75 0 0 1 .75-.75h14.5a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1-.75-.75Z" clip-rule="evenodd" /> </svg>
<div class="[ui-selected_&]:sr-only">{{ __('Center') }}</div>
</flux:editor.option>
<flux:editor.option value="right">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5 shrink-0" aria-hidden="true"> <path fill-rule="evenodd" d="M2 4.75A.75.75 0 0 1 2.75 4h14.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 4.75Zm7 10.5a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5a.75.75 0 0 1-.75-.75ZM2 10a.75.75 0 0 1 .75-.75h14.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 10Z" clip-rule="evenodd" /> </svg>
<div class="[ui-selected_&]:sr-only">{{ __('Right') }}</div>
</flux:editor.option>
</ui-options>
</ui-select>

View File

@@ -0,0 +1,11 @@
@blaze
@props([
'kbd' => '⌘+Shift+B',
])
<flux:tooltip content="{{ __('Blockquote') }}" :$kbd class="contents">
<flux:editor.button data-editor="blockquote">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> <path d="M14.1667 5H2.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M17.5 10H6.66666" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M17.5 15H6.66666" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M2.5 10V15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> </svg>
</flux:editor.button>
</flux:tooltip>

View File

@@ -0,0 +1,11 @@
@blaze
@props([
'kbd' => '⌘B',
])
<flux:tooltip content="{{ __('Bold') }}" :$kbd class="contents">
<flux:editor.button data-editor="bold">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5" aria-hidden="true"> <path fill-rule="evenodd" d="M4 3a1 1 0 0 1 1-1h6a4.5 4.5 0 0 1 3.274 7.587A4.75 4.75 0 0 1 11.25 18H5a1 1 0 0 1-1-1V3Zm2.5 5.5v-4H11a2 2 0 1 1 0 4H6.5Zm0 2.5v4.5h4.75a2.25 2.25 0 0 0 0-4.5H6.5Z" clip-rule="evenodd" /> </svg>
</flux:editor.button>
</flux:tooltip>

View File

@@ -0,0 +1,11 @@
@blaze
@props([
'kbd' => null,
])
<flux:tooltip content="{{ __('Bullet list') }}" :$kbd class="contents">
<flux:editor.button data-editor="bullet">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5" aria-hidden="true"> <path fill-rule="evenodd" d="M6 4.75A.75.75 0 0 1 6.75 4h10.5a.75.75 0 0 1 0 1.5H6.75A.75.75 0 0 1 6 4.75ZM6 10a.75.75 0 0 1 .75-.75h10.5a.75.75 0 0 1 0 1.5H6.75A.75.75 0 0 1 6 10Zm0 5.25a.75.75 0 0 1 .75-.75h10.5a.75.75 0 0 1 0 1.5H6.75a.75.75 0 0 1-.75-.75ZM1.99 4.75a1 1 0 0 1 1-1H3a1 1 0 0 1 1 1v.01a1 1 0 0 1-1 1h-.01a1 1 0 0 1-1-1v-.01ZM1.99 15.25a1 1 0 0 1 1-1H3a1 1 0 0 1 1 1v.01a1 1 0 0 1-1 1h-.01a1 1 0 0 1-1-1v-.01ZM1.99 10a1 1 0 0 1 1-1H3a1 1 0 0 1 1 1v.01a1 1 0 0 1-1 1h-.01a1 1 0 0 1-1-1V10Z" clip-rule="evenodd" /> </svg>
</flux:editor.button>
</flux:tooltip>

View File

@@ -0,0 +1,36 @@
@blaze
@php $iconVariant ??= $attributes->pluck('icon:variant'); @endphp
@props([
'iconVariant' => null,
'icon' => null,
])
@php
$iconClasses = Flux::classes()
// When using the outline icon variant, we need to size it down to match the default icon sizes...
->add($iconVariant === 'outline' ? ($slot->isEmpty() ? 'size-5' : 'size-4') : '')
;
$classes = Flux::classes()
->add('p-0.5 flex items-center justify-center text-sm font-medium rounded-sm touch-manipulation')
->add('text-zinc-400 data-open:text-zinc-800 hover:text-zinc-800 focus:text-zinc-800 data-match:text-zinc-800')
->add('disabled:opacity-75 dark:disabled:opacity-75 disabled:cursor-default disabled:pointer-events-none')
->add('dark:text-zinc-400 dark:data-open:text-white dark:hover:text-white dark:focus:text-white dark:data-match:text-white')
->add('hover:bg-zinc-200 hover:text-zinc-800')
->add('dark:hover:bg-white/10 dark:hover:text-white')
;
@endphp
<flux:with-tooltip :$attributes>
<button type="button" {{ $attributes->class($classes) }}>
<?php if (is_string($icon) && $icon !== ''): ?>
<flux:icon :$icon :variant="$iconVariant ?? ($slot->isEmpty() ? 'mini' : 'micro')" :class="$iconClasses" />
<?php elseif ($icon): ?>
{{ $icon }}
<?php endif; ?>
{{ $slot }}
</button>
</flux:with-tooltip>

View File

@@ -0,0 +1,11 @@
@blaze
@props([
'kbd' => '⌘E',
])
<flux:tooltip content="{{ __('Code') }}" :$kbd class="contents">
<flux:editor.button data-editor="code">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5" aria-hidden="true"> <path fill-rule="evenodd" d="M6.28 5.22a.75.75 0 0 1 0 1.06L2.56 10l3.72 3.72a.75.75 0 0 1-1.06 1.06L.97 10.53a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Zm7.44 0a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L17.44 10l-3.72-3.72a.75.75 0 0 1 0-1.06ZM11.377 2.011a.75.75 0 0 1 .612.867l-2.5 14.5a.75.75 0 0 1-1.478-.255l2.5-14.5a.75.75 0 0 1 .866-.612Z" clip-rule="evenodd" /> </svg>
</flux:editor.button>
</flux:tooltip>

View File

@@ -0,0 +1,3 @@
@blaze
<ui-editor-content {{ $attributes }} wire:ignore>{{ $slot }}</ui-editor-content>

View File

@@ -0,0 +1,40 @@
@blaze
@props([
'kbd' => null,
])
<ui-select data-editor="heading" class="relative">
<flux:tooltip content="{{ __('Styles') }}" :$kbd class="contents">
<flux:editor.button>
<ui-selected>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" class="size-5 shrink-0" aria-hidden="true"> <path d="M3.33331 5.83398V3.33398H16.6666V5.83398" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M7.5 16.666H12.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M10 3.33398V16.6673" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg>
<div class="[ui-selected_&]:sr-only">{{ __('Text') }}</div>
</ui-selected>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4" aria-hidden="true"> <path fill-rule="evenodd" d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" aria-hidden="true" /> </svg>
</flux:editor.button>
</flux:tooltip>
<ui-options popover="manual" aria-label="{{ __('Styles') }}" class="min-w-[10rem] rounded-lg border border-zinc-200 dark:border-zinc-600 bg-white dark:bg-zinc-700 shadow-xs p-[5px]">
<flux:editor.option value="paragraph" selected>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" class="size-5 shrink-0" aria-hidden="true"> <path d="M3.33331 5.83398V3.33398H16.6666V5.83398" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M7.5 16.666H12.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M10 3.33398V16.6673" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg>
<div class="[ui-selected_&]:sr-only">{{ __('Text') }}</div>
</flux:editor.option>
<flux:editor.option value="heading1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5 shrink-0" aria-hidden="true"> <path fill-rule="evenodd" d="M2.75 4a.75.75 0 0 1 .75.75v4.5h5v-4.5a.75.75 0 0 1 1.5 0v10.5a.75.75 0 0 1-1.5 0v-4.5h-5v4.5a.75.75 0 0 1-1.5 0V4.75A.75.75 0 0 1 2.75 4ZM13 8.75a.75.75 0 0 1 .75-.75h1.75a.75.75 0 0 1 .75.75v5.75h1a.75.75 0 0 1 0 1.5h-3.5a.75.75 0 0 1 0-1.5h1v-5h-1a.75.75 0 0 1-.75-.75Z" clip-rule="evenodd" /> </svg>
<div class="[ui-selected_&]:sr-only">{{ __('Heading 1') }}</div>
</flux:editor.option>
<flux:editor.option value="heading2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5 shrink-0" aria-hidden="true"> <path fill-rule="evenodd" d="M2.75 4a.75.75 0 0 1 .75.75v4.5h5v-4.5a.75.75 0 0 1 1.5 0v10.5a.75.75 0 0 1-1.5 0v-4.5h-5v4.5a.75.75 0 0 1-1.5 0V4.75A.75.75 0 0 1 2.75 4ZM15 9.5c-.729 0-1.445.051-2.146.15a.75.75 0 0 1-.208-1.486 16.887 16.887 0 0 1 3.824-.1c.855.074 1.512.78 1.527 1.637a17.476 17.476 0 0 1-.009.931 1.713 1.713 0 0 1-1.18 1.556l-2.453.818a1.25 1.25 0 0 0-.855 1.185v.309h3.75a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75v-1.059a2.75 2.75 0 0 1 1.88-2.608l2.454-.818c.102-.034.153-.117.155-.188a15.556 15.556 0 0 0 .009-.85.171.171 0 0 0-.158-.169A15.458 15.458 0 0 0 15 9.5Z" clip-rule="evenodd" /> </svg>
<div class="[ui-selected_&]:sr-only">{{ __('Heading 2') }}</div>
</flux:editor.option>
<flux:editor.option value="heading3">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5 shrink-0" aria-hidden="true"> <path fill-rule="evenodd" d="M2.75 4a.75.75 0 0 1 .75.75v4.5h5v-4.5a.75.75 0 0 1 1.5 0v10.5a.75.75 0 0 1-1.5 0v-4.5h-5v4.5a.75.75 0 0 1-1.5 0V4.75A.75.75 0 0 1 2.75 4ZM15 9.5c-.73 0-1.448.051-2.15.15a.75.75 0 1 1-.209-1.485 16.886 16.886 0 0 1 3.476-.128c.985.065 1.878.837 1.883 1.932V10a6.75 6.75 0 0 1-.301 2A6.75 6.75 0 0 1 18 14v.031c-.005 1.095-.898 1.867-1.883 1.932a17.018 17.018 0 0 1-3.467-.127.75.75 0 0 1 .209-1.485 15.377 15.377 0 0 0 3.16.115c.308-.02.48-.24.48-.441L16.5 14c0-.431-.052-.85-.15-1.25h-2.6a.75.75 0 0 1 0-1.5h2.6c.098-.4.15-.818.15-1.25v-.024c-.001-.201-.173-.422-.481-.443A15.485 15.485 0 0 0 15 9.5Z" clip-rule="evenodd" /> </svg>
<div class="[ui-selected_&]:sr-only">{{ __('Heading 3') }}</div>
</flux:editor.option>
</ui-options>
</ui-select>

View File

@@ -0,0 +1,11 @@
@blaze
@props([
'kbd' => null,
])
<flux:tooltip content="{{ __('Highlight') }}" :$kbd class="contents">
<flux:editor.button data-editor="highlight">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 11-6 6v3h9l3-3"/><path d="m22 12-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4"/></svg>
</flux:editor.button>
</flux:tooltip>

View File

@@ -0,0 +1,52 @@
@props([
'toolbar' => null,
'invalid' => null,
'variant' => null,
'name' => null,
])
@php
// We only want to show the name attribute on the checkbox if it has been set
// manually, but not if it has been set from the wire:model attribute...
$showName = isset($name);
if (! isset($name)) {
$name = $attributes->whereStartsWith('wire:model')->first();
}
$invalid ??= ($name && $errors->has($name));
$classes = Flux::classes()
->add('block w-full')
->add(match($variant) {
'borderless' => [
'**:data-[slot=content]:p-2!',
],
default => [
'shadow-xs [&[disabled]]:shadow-none border rounded-lg',
'bg-white dark:bg-white/10 dark:[&[disabled]]:bg-white/[7%]',
$invalid ? 'border-red-500' : 'border-zinc-200 border-b-zinc-300/80 dark:border-white/10',
],
})
->add('**:data-[slot=content]:text-base! sm:**:data-[slot=content]:text-sm!')
->add('**:data-[slot=content]:text-zinc-700 dark:**:data-[slot=content]:text-zinc-300')
->add('[&[disabled]_[data-slot=content]]:text-zinc-500 dark:[&[disabled]_[data-slot=content]]:text-zinc-400')
;
@endphp
<flux:with-field :$attributes>
<ui-editor {{ $attributes->class($classes) }} @if($showName) name="{{ $name }}" @endif aria-label="{{ __('Rich text editor') }}" data-flux-control data-flux-editor>
<?php if ($slot->isEmpty()): ?>
<flux:editor.toolbar :items="$toolbar" />
<flux:editor.content />
<?php else: ?>
{{ $slot }}
<?php endif; ?>
</ui-editor>
</flux:with-field>
@assets
<flux:editor.scripts />
<flux:editor.styles />
@endassets

View File

@@ -0,0 +1,11 @@
@blaze
@props([
'kbd' => '⌘I',
])
<flux:tooltip content="{{ __('Italic') }}" :$kbd class="contents">
<flux:editor.button data-editor="italic">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5" aria-hidden="true"> <path fill-rule="evenodd" d="M8 2.75A.75.75 0 0 1 8.75 2h7.5a.75.75 0 0 1 0 1.5h-3.215l-4.483 13h2.698a.75.75 0 0 1 0 1.5h-7.5a.75.75 0 0 1 0-1.5h3.215l4.483-13H8.75A.75.75 0 0 1 8 2.75Z" clip-rule="evenodd" /> </svg>
</flux:editor.button>
</flux:tooltip>

View File

@@ -0,0 +1,33 @@
@blaze
@props([
'kbd' => '⌘K',
])
<flux:dropdown position="bottom center" data-editor="link" class="contents">
<flux:tooltip content="{{ __('Insert link') }}" :$kbd class="contents">
<flux:editor.button data-match-target>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> <path d="M7.49999 14.1673H5.83332C4.72825 14.1673 3.66845 13.7283 2.88704 12.9469C2.10564 12.1655 1.66666 11.1057 1.66666 10.0007C1.66666 8.89558 2.10564 7.83577 2.88704 7.05437C3.66845 6.27297 4.72825 5.83398 5.83332 5.83398H7.49999" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M12.5 5.83398H14.1667C15.2717 5.83398 16.3315 6.27297 17.1129 7.05437C17.8943 7.83577 18.3333 8.89558 18.3333 10.0007C18.3333 11.1057 17.8943 12.1655 17.1129 12.9469C16.3315 13.7283 15.2717 14.1673 14.1667 14.1673H12.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M6.66666 10H13.3333" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> </svg>
</flux:editor.button>
</flux:tooltip>
<div popover="manual" class="min-w-[360px] p-[5px] rounded-lg border border-zinc-200 dark:border-zinc-600 shadow-xs bg-white dark:bg-zinc-700">
<div class="h-8 flex justify-between gap-2 ps-2 pe-1" data-flux-editor-link>
<input data-editor="link:url" type="text" form="" placeholder="https://..." class="flex-1 text-base sm:text-sm outline-hidden bg-transparent" autofocus>
<div class="flex gap-2 items-center">
<flux:tooltip content="{{ __('Insert link') }}" class="contents">
<button type="button" data-editor="link:insert" class="p-0.5 text-sm font-medium text-zinc-400 rounded-sm [[data-flux-editor-link]:not(:has(input:placeholder-shown))_&:hover]:bg-zinc-200 dark:[[data-flux-editor-link]:not(:has(input:placeholder-shown))_&:hover]:bg-white/10 [[data-flux-editor-link]:not(:has(input:placeholder-shown))_&]:text-zinc-800 dark:[[data-flux-editor-link]:not(:has(input:placeholder-shown))_&]:text-white">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-5 shrink-0" aria-hidden="true"> <path fill-rule="evenodd" d="M19.916 4.626a.75.75 0 0 1 .208 1.04l-9 13.5a.75.75 0 0 1-1.154.114l-6-6a.75.75 0 0 1 1.06-1.06l5.353 5.353 8.493-12.74a.75.75 0 0 1 1.04-.207Z" clip-rule="evenodd" /> </svg> <path fill-rule="evenodd" d="M16.704 4.153a.75.75 0 0 1 .143 1.052l-8 10.5a.75.75 0 0 1-1.127.075l-4.5-4.5a.75.75 0 0 1 1.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 0 1 1.05-.143Z" clip-rule="evenodd" /> </svg>
</button>
</flux:tooltip>
<flux:tooltip content="{{ __('Unlink') }}" class="contents">
<button type="button" data-editor="link:unlink" class="p-0.5 text-sm font-medium text-zinc-400 rounded-sm [[data-flux-editor-link]:not(:has(input:placeholder-shown))_&:hover]:bg-zinc-200 dark:[[data-flux-editor-link]:not(:has(input:placeholder-shown))_&:hover]:bg-white/10 [[data-flux-editor-link]:not(:has(input:placeholder-shown))_&]:text-zinc-800 dark:[[data-flux-editor-link]:not(:has(input:placeholder-shown))_&]:text-white">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" class="size-5 shrink-0" aria-hidden="true"> <g clip-path="url(#clip0_1014_3807)"> <path d="M15.7 10.2082L17.1334 8.78317H17.1167C17.8839 7.9882 18.3042 6.92127 18.2855 5.81664C18.2668 4.71202 17.8104 3.65997 17.0167 2.8915C16.2391 2.14166 15.2011 1.72266 14.1209 1.72266C13.0407 1.72266 12.0026 2.14166 11.225 2.8915L9.79169 4.3165" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M4.30834 9.79102L2.88334 11.216C2.11615 12.011 1.69578 13.0779 1.71453 14.1825C1.73328 15.2872 2.18961 16.3392 2.98334 17.1077C3.76089 17.8575 4.79898 18.2765 5.87918 18.2765C6.95938 18.2765 7.99747 17.8575 8.77501 17.1077L10.2 15.6827" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M6.66669 1.66602V4.16602" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M1.66669 6.66602H4.16669" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M13.3333 15.834V18.334" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M15.8333 13.334H18.3333" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> </g> <defs> <clipPath id="clip0_1014_3807"> <rect width="20" height="20" fill="white"/> </clipPath> </defs> </svg>
</button>
</flux:tooltip>
</div>
</div>
</div>
</flux:dropdown>

View File

@@ -0,0 +1,5 @@
@blaze
<ui-option {{ $attributes }} class="h-8 px-2 flex items-center gap-2 rounded-lg text-sm font-medium text-zinc-800 dark:text-white data-active:bg-zinc-50 dark:data-active:bg-zinc-600 [&>svg]:text-zinc-400 [&[data-active]>svg]:text-zinc-800 dark:[&[data-active]>svg]:text-white">
{{ $slot }}
</ui-option>

View File

@@ -0,0 +1,11 @@
@blaze
@props([
'kbd' => null,
])
<flux:tooltip content="{{ __('Ordered list') }}" :$kbd class="contents">
<flux:editor.button data-editor="ordered">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> <path d="M8.33334 10H17.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M8.33334 15H17.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M8.33334 5H17.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M3.33334 8.33398H5.00001" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M3.33334 5H4.16668V8.33333" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M5.00001 15.0007H3.33334C3.33334 14.1674 5.00001 13.334 5.00001 12.5007C5.00001 11.6674 4.16668 11.2507 3.33334 11.6674" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> </svg>
</flux:editor.button>
</flux:tooltip>

View File

@@ -0,0 +1,11 @@
@blaze
@props([
'kbd' => '⌘+Shift+Z',
])
<flux:tooltip content="{{ __('Redo') }}" :$kbd class="contents">
<flux:editor.button data-editor="redo">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 7v6h-6"/><path d="M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3l3 2.7"/></svg>
</flux:editor.button>
</flux:tooltip>

View File

@@ -0,0 +1,2 @@
{!! app('flux')->editorScripts() !!}

View File

@@ -0,0 +1,3 @@
@blaze
<div data-orientation="vertical" role="none" class="h-4 border-0 bg-zinc-200 dark:bg-white/20 w-px"></div>

View File

@@ -0,0 +1,3 @@
@blaze
<div class="flex-1" role="none"></div>

View File

@@ -0,0 +1,11 @@
@blaze
@props([
'kbd' => '⌘+Shift+S',
])
<flux:tooltip content="{{ __('Strikethrough') }}" :$kbd class="contents">
<flux:editor.button data-editor="strike">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5" aria-hidden="true"> <path fill-rule="evenodd" d="M11.617 3.963c-1.186-.318-2.418-.323-3.416.015-.992.336-1.49.91-1.642 1.476-.152.566-.007 1.313.684 2.1.528.6 1.273 1.1 2.128 1.446h7.879a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1 0-1.5h3.813a5.976 5.976 0 0 1-.447-.456C5.18 7.479 4.798 6.231 5.11 5.066c.312-1.164 1.268-2.055 2.61-2.509 1.336-.451 2.877-.42 4.286-.043.856.23 1.684.592 2.409 1.074a.75.75 0 1 1-.83 1.25 6.723 6.723 0 0 0-1.968-.875Zm1.909 8.123a.75.75 0 0 1 1.015.309c.53.99.607 2.062.18 3.01-.421.94-1.289 1.648-2.441 2.038-1.336.452-2.877.42-4.286.043-1.409-.377-2.759-1.121-3.69-2.18a.75.75 0 1 1 1.127-.99c.696.791 1.765 1.403 2.952 1.721 1.186.318 2.418.323 3.416-.015.853-.288 1.34-.756 1.555-1.232.21-.467.205-1.049-.136-1.69a.75.75 0 0 1 .308-1.014Z" clip-rule="evenodd" /> </svg>
</flux:editor.button>
</flux:tooltip>

View File

@@ -0,0 +1,2 @@
{!! app('flux')->editorStyles() !!}

View File

@@ -0,0 +1,11 @@
@blaze
@props([
'kbd' => null,
])
<flux:tooltip content="{{ __('Subscript') }}" :$kbd class="contents">
<flux:editor.button data-editor="subscript">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m4 5 8 8"/><path d="m12 5-8 8"/><path d="M20 19h-4c0-1.5.44-2 1.5-2.5S20 15.33 20 14c0-.47-.17-.93-.48-1.29a2.11 2.11 0 0 0-2.62-.44c-.42.24-.74.62-.9 1.07"/></svg>
</flux:editor.button>
</flux:tooltip>

View File

@@ -0,0 +1,11 @@
@blaze
@props([
'kbd' => null,
])
<flux:tooltip content="{{ __('Superscript') }}" :$kbd class="contents">
<flux:editor.button data-editor="superscript">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m4 19 8-8"/><path d="m12 19-8-8"/><path d="M20 12h-4c0-1.5.442-2 1.5-2.5S20 8.334 20 7.002c0-.472-.17-.93-.484-1.29a2.105 2.105 0 0 0-2.617-.436c-.42.239-.738.614-.899 1.06"/></svg>
</flux:editor.button>
</flux:tooltip>

View File

@@ -0,0 +1,50 @@
@aware(['variant' => null])
@props([
'items' => null,
'variant' => null,
])
@php
$classes = Flux::classes()
->add('block overflow-x-auto w-full')
->add(match($variant) {
'borderless' => 'rounded-lg bg-zinc-100 dark:bg-white/10 *:p-1.5 *:h-auto',
default => [
'bg-zinc-50 dark:bg-white/[6%] dark:border-white/5',
'rounded-t-[calc(0.5rem-1px)]',
'border-b border-zinc-200 dark:border-white/10',
]
})
;
@endphp
<ui-toolbar {{ $attributes->class($classes) }} wire:ignore aria-label="{{ __('Formatting') }}">
<div class="h-10 p-2 flex gap-2 items-center">
<?php if ($slot->isNotEmpty()): ?>
{{ $slot }}
<?php else: ?>
<?php if ($items !== null): ?>
<?php foreach (str($items)->explode(' ') as $item): ?>
<?php if ($item === '|') $item = 'separator'; ?>
<?php if ($item === '~') $item = 'spacer'; ?>
<flux:delegate-component :component="'editor.' . $item"></flux:delegate-component>
<?php endforeach; ?>
<?php else: ?>
<flux:editor.heading />
<flux:editor.separator />
<flux:editor.bold />
<flux:editor.italic />
<flux:editor.strike />
<flux:editor.separator />
<flux:editor.bullet />
<flux:editor.ordered />
<flux:editor.blockquote />
<flux:editor.separator />
<flux:editor.link />
<flux:editor.separator />
<flux:editor.align />
<?php endif; ?>
<?php endif; ?>
</div>
</ui-toolbar>

View File

@@ -0,0 +1,11 @@
@blaze
@props([
'kbd' => '⌘U',
])
<flux:tooltip content="{{ __('Underline') }}" :$kbd class="contents">
<flux:editor.button data-editor="underline">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5" aria-hidden="true"> <path fill-rule="evenodd" d="M4.75 2a.75.75 0 0 1 .75.75V9a4.5 4.5 0 1 0 9 0V2.75a.75.75 0 0 1 1.5 0V9A6 6 0 0 1 4 9V2.75A.75.75 0 0 1 4.75 2ZM2 17.25a.75.75 0 0 1 .75-.75h14.5a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1-.75-.75Z" clip-rule="evenodd" /> </svg>
</flux:editor.button>
</flux:tooltip>

Some files were not shown because too many files have changed in this diff Show More