Scheduling
Time and calendar views — a Gantt schedule, a weekly grid and a year overview.
Preview
@props([
'heading' => 'Weekly availability',
'subheading' => 'A weekly schedule designer — green spans mark open time, striped regions are unavailable.',
'days' => null,
'hours' => null,
'spans' => null,
'disabled' => null,
])
@php
// Week-calendar availability designer (abui "Availability Designer" shape):
// day columns × hour rows, multi-hour availability spans, striped disabled
// regions and dimmed weekends. CSS-grid placement keeps it RTL-safe and the
// Stripe uses a full-color alias with an HSL compatibility fallback (spec §18 — no raw hex).
$days ??= ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
$hours ??= ['8:00', '9:00', '10:00', '11:00', '12:00', '13:00', '14:00', '15:00', '16:00', '17:00', '18:00'];
$weekend = [0, 6];
// Availability spans: [dayIndex, startHourIndex, durationHours, startLabel, durationLabel, endLabel].
$spans ??= [
['day' => 1, 'start' => 1, 'duration' => 3, 'from' => '09:00', 'length' => '3h', 'to' => '12:00'],
['day' => 3, 'start' => 6, 'duration' => 2, 'from' => '14:00', 'length' => '2h', 'to' => '16:00'],
['day' => 5, 'start' => 2, 'duration' => 2, 'from' => '10:00', 'length' => '2h', 'to' => '12:00'],
['day' => 2, 'start' => 8, 'duration' => 1, 'from' => '16:00', 'length' => '1h', 'to' => '17:00'],
];
// Disabled regions: [dayIndex, startHourIndex, durationHours]. Weekends are
// filled below; weekdays get a midday lunch block.
$disabled ??= [
['day' => 1, 'start' => 4, 'duration' => 1],
['day' => 2, 'start' => 4, 'duration' => 1],
['day' => 3, 'start' => 4, 'duration' => 1],
['day' => 4, 'start' => 4, 'duration' => 1],
['day' => 5, 'start' => 4, 'duration' => 1],
];
$rowCount = count($hours);
$gridCols = 'grid-template-columns: minmax(3.5rem, auto) repeat('.count($days).', minmax(0, 1fr)); grid-template-rows: auto repeat('.$rowCount.', minmax(2.75rem, 1fr));';
// Faint diagonal hatching from the muted-foreground token — inline so it can
// reference the CSS variable without an arbitrary raw-colour utility.
$stripe = 'background-image: repeating-linear-gradient(-45deg, color-mix(in oklab, var(--muted-foreground-color, hsl(var(--muted-foreground))) calc(0.18 * 100%), transparent) 0, color-mix(in oklab, var(--muted-foreground-color, hsl(var(--muted-foreground))) calc(0.18 * 100%), transparent) 1px, transparent 1px, transparent 7px);';
@endphp
<x-ui.card {{ $attributes->merge(['class' => 'overflow-hidden']) }}>
<x-ui.card.header>
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
<div>
<x-ui.card.title class="text-lg">{{ __($heading) }}</x-ui.card.title>
@if ($subheading)
<x-ui.card.description class="mt-2">{{ __($subheading) }}</x-ui.card.description>
@endif
</div>
<div class="flex flex-wrap items-center gap-2">
<x-ui.badge variant="outline" class="gap-2">
<span class="size-2 rounded-full bg-success" aria-hidden="true"></span>
{{ __('Available') }}
</x-ui.badge>
<x-ui.badge variant="outline" class="gap-2">
<span class="size-2 rounded-[3px] border border-border" style="{{ $stripe }}" aria-hidden="true"></span>
{{ __('Unavailable') }}
</x-ui.badge>
</div>
</div>
</x-ui.card.header>
<x-ui.card.content>
<div class="overflow-x-auto">
<div role="region" aria-label="{{ __($heading) }}" class="relative grid min-w-[44rem] text-sm" style="{{ $gridCols }}">
{{-- Day header row --}}
<div class="border-b border-border" style="grid-row: 1; grid-column: 1;"></div>
@foreach ($days as $d => $day)
<div
@class([
'border-b border-s border-border px-2 py-2 text-center text-xs font-semibold',
'text-muted-foreground' => in_array($d, $weekend, true),
'text-foreground' => ! in_array($d, $weekend, true),
])
style="grid-row: 1; grid-column: {{ $d + 2 }};"
>{{ __($day) }}</div>
@endforeach
{{-- Time labels + base cells (gridlines + weekend tint) --}}
@foreach ($hours as $h => $hour)
<div class="-mt-2 pe-2 text-end text-xs text-muted-foreground" style="grid-row: {{ $h + 2 }}; grid-column: 1;">{{ $hour }}</div>
@foreach ($days as $d => $day)
<div
@class([
'border-s border-t border-border',
'bg-muted/40' => in_array($d, $weekend, true),
])
style="grid-row: {{ $h + 2 }}; grid-column: {{ $d + 2 }};"
></div>
@endforeach
@endforeach
{{-- Weekend columns: fully unavailable (striped overlay) --}}
@foreach ($weekend as $d)
<div class="pointer-events-none" style="{{ $stripe }} grid-row: 2 / span {{ $rowCount }}; grid-column: {{ $d + 2 }};" aria-hidden="true"></div>
@endforeach
{{-- Disabled weekday blocks --}}
@foreach ($disabled as $block)
@php $bd = (int) $block['day']; $bs = (int) $block['start']; $bn = max(1, (int) $block['duration']); @endphp
<div class="pointer-events-none" style="{{ $stripe }} grid-row: {{ $bs + 2 }} / span {{ $bn }}; grid-column: {{ $bd + 2 }};" aria-hidden="true"></div>
@endforeach
{{-- Availability spans --}}
@foreach ($spans as $span)
@php
$sd = (int) $span['day'];
$ss = (int) $span['start'];
$sn = max(1, min((int) $span['duration'], $rowCount - $ss));
@endphp
<div
class="z-10 m-0.5 flex flex-col rounded-md border border-success bg-success/15 p-2 shadow-sm"
style="grid-row: {{ $ss + 2 }} / span {{ $sn }}; grid-column: {{ $sd + 2 }};"
>
<div class="flex items-center justify-between gap-2">
<span class="text-xs font-semibold text-success">{{ $span['from'] }}</span>
<span class="flex size-4 items-center justify-center rounded-sm text-muted-foreground" aria-hidden="true">
<svg class="size-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18M6 6l12 12"/></svg>
</span>
</div>
<span class="flex items-center gap-2 text-xs text-muted-foreground">
<svg class="size-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
{{ $span['length'] }}
</span>
<span class="mt-auto text-xs font-medium text-foreground">{{ $span['to'] }}</span>
</div>
@endforeach
</div>
</div>
<p class="mt-4 text-xs text-muted-foreground">{{ __('Striped regions are unavailable — spans cannot be moved into lunch breaks or the weekend.') }}</p>
</x-ui.card.content>
</x-ui.card>
Installation
php artisan ui:add blocks/scheduling-weekly
Registry contract
Install confidence
php artisan ui:add blocks/scheduling-weekly
writes only the generated targets below. The CLI validates each file hash before writing and
prompts before replacing local changes unless --force is used.
- Version
- 1.0.5
- License
- open
- Stability
- stable
- Contract
- v1
- Foundation
- ≥ 1.0.0
| Type | Generated target |
|---|---|
| blade | resources/views/blocks/scheduling-weekly.blade.php |
Registry dependencies
Package dependencies
composer: jml/brok:^0.2
Use with AI
A brief for your coding agent: what the block is, the install command, how to render it, its props and the rules. Copy it, or open a prompt about this block in an assistant.
# Brok UI block: Availability (`scheduling-weekly`)
A weekly availability designer: day columns and hour rows with multi-hour green availability spans, diagonal-striped unavailable regions and dimmed weekends.
Brok UI is a Laravel Blade registry. `ui:add` copies this block into the app as plain Blade the app owns; it composes installed `<brok:*>` primitives and semantic design tokens.
## Install
```bash
php artisan ui:add blocks/scheduling-weekly
```
## Render it
```blade
<x-blocks.scheduling-weekly />
```
## Props
- `heading` (string, default `Weekly availability`) — Section heading.
- `subheading` (string, default `A weekly schedule designer — green spans mark open time, striped regions are unavailable.`) — Supporting copy under the heading.
- `days` (mixed|null, default `null`) — Weekday column labels; null falls back to Sun-Sat.
- `hours` (mixed|null, default `null`) — Hour row labels; null falls back to an 8am-6pm sample range.
- `spans` (mixed|null, default `null`) — Multi-hour availability spans placed on the grid; null falls back to sample data.
- `disabled` (mixed|null, default `null`) — Striped unavailable regions placed on the grid; null falls back to a sample midday block per weekday.
## Use when
- Letting someone review or design a weekly availability grid of open and unavailable hours across days.
- Wanting a single calendar of day columns and hour rows rather than multiple resources at once.
## Avoid when
- Scheduling multiple rooms or resources at once; use scheduling-gantt instead.
## Rules
- Render the installed block with `<x-blocks.scheduling-weekly />` and pass data through its props; edit the copied file only for structural changes.
- Keep the semantic design tokens (`bg-background`, `text-muted-foreground`); never swap in raw colour utilities.
- Keep the `data-slot` attributes and the logical (start/end) spacing so the markup still mirrors under `dir="rtl"`.
## Links
- Docs: https://brokui.dev/blocks/scheduling-weekly
- Registry JSON (files, props, contract): https://brokui.dev/r/open/blocks/scheduling-weekly.json
Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.
Guidance
Use when
- Letting someone review or design a weekly availability grid of open and unavailable hours across days.
- Wanting a single calendar of day columns and hour rows rather than multiple resources at once.
Avoid when
- Scheduling multiple rooms or resources at once; use scheduling-gantt instead.
Anti-patterns
- Do not replace semantic props with conflicting utility classes.
- Do not remove labels, focus styles, or state attributes.
Usage
Render the block as a component, passing data where useful:
<x-blocks.scheduling-weekly />
Built from primitives
This block composes installed <brok:*> primitives and semantic design
tokens only — it does not reimplement any primitive. Re-theme it (light, dark, admin, customer) by
editing CSS variables; flip the preview to RTL to confirm it mirrors.
Source
The exact, editable file ui:add writes
into your app. The preview above renders this same source — there are no preview-only blocks.
@props([
'heading' => 'Weekly availability',
'subheading' => 'A weekly schedule designer — green spans mark open time, striped regions are unavailable.',
'days' => null,
'hours' => null,
'spans' => null,
'disabled' => null,
])
@php
// Week-calendar availability designer (abui "Availability Designer" shape):
// day columns × hour rows, multi-hour availability spans, striped disabled
// regions and dimmed weekends. CSS-grid placement keeps it RTL-safe and the
// Stripe uses a full-color alias with an HSL compatibility fallback (spec §18 — no raw hex).
$days ??= ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
$hours ??= ['8:00', '9:00', '10:00', '11:00', '12:00', '13:00', '14:00', '15:00', '16:00', '17:00', '18:00'];
$weekend = [0, 6];
// Availability spans: [dayIndex, startHourIndex, durationHours, startLabel, durationLabel, endLabel].
$spans ??= [
['day' => 1, 'start' => 1, 'duration' => 3, 'from' => '09:00', 'length' => '3h', 'to' => '12:00'],
['day' => 3, 'start' => 6, 'duration' => 2, 'from' => '14:00', 'length' => '2h', 'to' => '16:00'],
['day' => 5, 'start' => 2, 'duration' => 2, 'from' => '10:00', 'length' => '2h', 'to' => '12:00'],
['day' => 2, 'start' => 8, 'duration' => 1, 'from' => '16:00', 'length' => '1h', 'to' => '17:00'],
];
// Disabled regions: [dayIndex, startHourIndex, durationHours]. Weekends are
// filled below; weekdays get a midday lunch block.
$disabled ??= [
['day' => 1, 'start' => 4, 'duration' => 1],
['day' => 2, 'start' => 4, 'duration' => 1],
['day' => 3, 'start' => 4, 'duration' => 1],
['day' => 4, 'start' => 4, 'duration' => 1],
['day' => 5, 'start' => 4, 'duration' => 1],
];
$rowCount = count($hours);
$gridCols = 'grid-template-columns: minmax(3.5rem, auto) repeat('.count($days).', minmax(0, 1fr)); grid-template-rows: auto repeat('.$rowCount.', minmax(2.75rem, 1fr));';
// Faint diagonal hatching from the muted-foreground token — inline so it can
// reference the CSS variable without an arbitrary raw-colour utility.
$stripe = 'background-image: repeating-linear-gradient(-45deg, color-mix(in oklab, var(--muted-foreground-color, hsl(var(--muted-foreground))) calc(0.18 * 100%), transparent) 0, color-mix(in oklab, var(--muted-foreground-color, hsl(var(--muted-foreground))) calc(0.18 * 100%), transparent) 1px, transparent 1px, transparent 7px);';
@endphp
<x-ui.card {{ $attributes->merge(['class' => 'overflow-hidden']) }}>
<x-ui.card.header>
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
<div>
<x-ui.card.title class="text-lg">{{ __($heading) }}</x-ui.card.title>
@if ($subheading)
<x-ui.card.description class="mt-2">{{ __($subheading) }}</x-ui.card.description>
@endif
</div>
<div class="flex flex-wrap items-center gap-2">
<x-ui.badge variant="outline" class="gap-2">
<span class="size-2 rounded-full bg-success" aria-hidden="true"></span>
{{ __('Available') }}
</x-ui.badge>
<x-ui.badge variant="outline" class="gap-2">
<span class="size-2 rounded-[3px] border border-border" style="{{ $stripe }}" aria-hidden="true"></span>
{{ __('Unavailable') }}
</x-ui.badge>
</div>
</div>
</x-ui.card.header>
<x-ui.card.content>
<div class="overflow-x-auto">
<div role="region" aria-label="{{ __($heading) }}" class="relative grid min-w-[44rem] text-sm" style="{{ $gridCols }}">
{{-- Day header row --}}
<div class="border-b border-border" style="grid-row: 1; grid-column: 1;"></div>
@foreach ($days as $d => $day)
<div
@class([
'border-b border-s border-border px-2 py-2 text-center text-xs font-semibold',
'text-muted-foreground' => in_array($d, $weekend, true),
'text-foreground' => ! in_array($d, $weekend, true),
])
style="grid-row: 1; grid-column: {{ $d + 2 }};"
>{{ __($day) }}</div>
@endforeach
{{-- Time labels + base cells (gridlines + weekend tint) --}}
@foreach ($hours as $h => $hour)
<div class="-mt-2 pe-2 text-end text-xs text-muted-foreground" style="grid-row: {{ $h + 2 }}; grid-column: 1;">{{ $hour }}</div>
@foreach ($days as $d => $day)
<div
@class([
'border-s border-t border-border',
'bg-muted/40' => in_array($d, $weekend, true),
])
style="grid-row: {{ $h + 2 }}; grid-column: {{ $d + 2 }};"
></div>
@endforeach
@endforeach
{{-- Weekend columns: fully unavailable (striped overlay) --}}
@foreach ($weekend as $d)
<div class="pointer-events-none" style="{{ $stripe }} grid-row: 2 / span {{ $rowCount }}; grid-column: {{ $d + 2 }};" aria-hidden="true"></div>
@endforeach
{{-- Disabled weekday blocks --}}
@foreach ($disabled as $block)
@php $bd = (int) $block['day']; $bs = (int) $block['start']; $bn = max(1, (int) $block['duration']); @endphp
<div class="pointer-events-none" style="{{ $stripe }} grid-row: {{ $bs + 2 }} / span {{ $bn }}; grid-column: {{ $bd + 2 }};" aria-hidden="true"></div>
@endforeach
{{-- Availability spans --}}
@foreach ($spans as $span)
@php
$sd = (int) $span['day'];
$ss = (int) $span['start'];
$sn = max(1, min((int) $span['duration'], $rowCount - $ss));
@endphp
<div
class="z-10 m-0.5 flex flex-col rounded-md border border-success bg-success/15 p-2 shadow-sm"
style="grid-row: {{ $ss + 2 }} / span {{ $sn }}; grid-column: {{ $sd + 2 }};"
>
<div class="flex items-center justify-between gap-2">
<span class="text-xs font-semibold text-success">{{ $span['from'] }}</span>
<span class="flex size-4 items-center justify-center rounded-sm text-muted-foreground" aria-hidden="true">
<svg class="size-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18M6 6l12 12"/></svg>
</span>
</div>
<span class="flex items-center gap-2 text-xs text-muted-foreground">
<svg class="size-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
{{ $span['length'] }}
</span>
<span class="mt-auto text-xs font-medium text-foreground">{{ $span['to'] }}</span>
</div>
@endforeach
</div>
</div>
<p class="mt-4 text-xs text-muted-foreground">{{ __('Striped regions are unavailable — spans cannot be moved into lunch breaks or the weekend.') }}</p>
</x-ui.card.content>
</x-ui.card>
Ownership & lifecycle
Owner, release state, review evidence and adoption for this item.
- Owner
- Platform UI (@JoshJML)
- Current version
-
1.0.5 - Status
- Stable
- License
-
open - Deprecation
- Not deprecated
- Contract
-
v1 - Foundation
-
≥ 1.0.0