Solves CSS riddle with perfect group buttons, changes layout to better handle content pane, adds calendar date navigation
This commit is contained in:
parent
9f3ceabd7d
commit
a4adab31d2
@ -36,7 +36,12 @@ class CalendarController extends Controller
|
||||
// get the view and time range
|
||||
[$view, $range] = $this->resolveRange($request);
|
||||
|
||||
// get the user's selected calendars
|
||||
// date range controls
|
||||
$prev = $range['start']->copy()->subMonth()->startOfMonth()->toDateString();
|
||||
$next = $range['start']->copy()->addMonth()->startOfMonth()->toDateString();
|
||||
$today = Carbon::today()->toDateString();
|
||||
|
||||
// get the user's visible calendars from the left bar
|
||||
$visible = collect($request->query('c', []));
|
||||
|
||||
// load the user's calendars
|
||||
@ -106,10 +111,15 @@ class CalendarController extends Controller
|
||||
$payload = [
|
||||
'view' => $view,
|
||||
'range' => $range,
|
||||
'active' => [
|
||||
'nav' => [
|
||||
'prev' => $prev,
|
||||
'next' => $next,
|
||||
'today' => $today,
|
||||
],
|
||||
'active' => [
|
||||
'year' => $range['start']->format('Y'),
|
||||
'month' => $range['start']->format("F"),
|
||||
'day' => $range['start']->format("d"),
|
||||
'day' => $range['start']->format("d"),
|
||||
],
|
||||
'calendars' => $calendar_map->map(function ($cal) {
|
||||
return [
|
||||
|
@ -22,118 +22,133 @@ body {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* primary app navigation on the left */
|
||||
nav {
|
||||
@apply w-20 flex flex-col items-center justify-between;
|
||||
/* primary app navigation on the left */
|
||||
> nav {
|
||||
@apply w-20 flex flex-col items-center justify-between;
|
||||
|
||||
/* top items */
|
||||
.top {
|
||||
@apply flex flex-col items-center pt-6 2xl:pt-8 mt-2px;
|
||||
}
|
||||
/* top items */
|
||||
.top {
|
||||
@apply flex flex-col items-center pt-6 2xl:pt-8 mt-2px;
|
||||
}
|
||||
|
||||
/* bottom items */
|
||||
/* bottom items */
|
||||
|
||||
.bottom {
|
||||
@apply pb-6 2xl:pb-8;
|
||||
}
|
||||
.bottom {
|
||||
@apply pb-6 2xl:pb-8;
|
||||
}
|
||||
|
||||
/* app buttons */
|
||||
menu {
|
||||
@apply flex flex-col gap-1 items-center mt-6;
|
||||
/* app buttons */
|
||||
menu {
|
||||
@apply flex flex-col gap-1 items-center mt-6;
|
||||
|
||||
li.app-button {
|
||||
li.app-button {
|
||||
|
||||
a {
|
||||
@apply flex items-center justify-center p-3 bg-transparent text-black;
|
||||
transition: background-color 100ms ease-in-out;
|
||||
border-radius: 70% 50% 70% 30% / 60% 60% 60% 40%; /* blob 1 */
|
||||
|
||||
&:hover {
|
||||
@apply bg-gray-200;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
@apply bg-cyan-400;
|
||||
a {
|
||||
@apply flex items-center justify-center p-3 bg-transparent text-black;
|
||||
transition: background-color 100ms ease-in-out;
|
||||
border-radius: 70% 50% 70% 30% / 60% 60% 60% 40%; /* blob 1 */
|
||||
|
||||
&:hover {
|
||||
@apply bg-cyan-500;
|
||||
@apply bg-gray-200;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
@apply bg-cyan-400;
|
||||
|
||||
&:hover {
|
||||
@apply bg-cyan-500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(2) a {
|
||||
border-radius: 70% 30% 30% 70% / 60% 40% 60% 40%; /* blob 2 */
|
||||
}
|
||||
&:nth-child(2) a {
|
||||
border-radius: 70% 30% 30% 70% / 60% 40% 60% 40%; /* blob 2 */
|
||||
}
|
||||
|
||||
&:nth-child(3) a {
|
||||
border-radius: 80% 65% 90% 50% / 90% 80% 75% 75%; /* blob 3 */
|
||||
&:nth-child(3) a {
|
||||
border-radius: 80% 65% 90% 50% / 90% 80% 75% 75%; /* blob 3 */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* primary content window defaults */
|
||||
/*
|
||||
* primary content window defaults
|
||||
*
|
||||
* main
|
||||
* aside (optional left bar, must include h1 page title)
|
||||
* article (content pane; if no aside, header includes h1)
|
||||
* header
|
||||
* section
|
||||
*/
|
||||
main {
|
||||
@apply rounded-lg bg-white;
|
||||
|
||||
/* app */
|
||||
body#app & {
|
||||
@apply grid m-2 ml-0;
|
||||
grid-template-rows: 5rem auto;
|
||||
@apply grid grid-cols-1 m-2 ml-0;
|
||||
|
||||
/* if there's an aside, set the cols */
|
||||
&:has(aside) {
|
||||
grid-template-columns: minmax(20rem, 20dvw) auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* auth screens */
|
||||
body#auth & {
|
||||
@apply w-1/2 mx-auto p-8;
|
||||
min-width: 16rem;
|
||||
max-width: 40rem;
|
||||
}
|
||||
|
||||
/* main content title and actions */
|
||||
> header {
|
||||
@apply grid items-center;
|
||||
grid-template-columns: minmax(20rem, 20dvw) repeat(3, 1fr);
|
||||
/* left column */
|
||||
aside {
|
||||
@apply flex flex-col col-span-1 px-6 2xl:px-8 pb-8 h-full;
|
||||
|
||||
h1 {
|
||||
@apply flex items-center pl-6 2xl:pl-8;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply col-span-1 flex flex-row gap-1 items-center justify-start relative top-px;
|
||||
|
||||
> span {
|
||||
@apply text-gray-700;
|
||||
}
|
||||
}
|
||||
|
||||
menu {
|
||||
@apply col-span-2 flex flex-row items-center justify-end gap-2 pr-6 2xl:pr-8;
|
||||
> h1 {
|
||||
@apply flex items-center h-20;
|
||||
}
|
||||
}
|
||||
|
||||
/* main content wrapper */
|
||||
> article {
|
||||
@apply grid w-full;
|
||||
grid-template-columns: minmax(20rem, 20dvw) repeat(3, 1fr);
|
||||
article {
|
||||
@apply grid grid-cols-1 w-full;
|
||||
grid-template-rows: 5rem auto;
|
||||
|
||||
/* left column */
|
||||
aside {
|
||||
@apply col-span-1 px-6 2xl:px-8 h-full;
|
||||
}
|
||||
/* main content title and actions */
|
||||
> header {
|
||||
@apply flex flex-row items-center justify-between w-full;
|
||||
|
||||
/* calendar page defaults */
|
||||
&#calendar {
|
||||
/* if h1 exists it means there's no aside, so force the width from that */
|
||||
h1 {
|
||||
@apply flex items-center pl-6 2xl:pl-8;
|
||||
width: minmax(20rem, 20dvw);
|
||||
}
|
||||
|
||||
aside {
|
||||
@apply grid pb-6 2xl:pb-8;
|
||||
grid-template-rows: 1fr min-content;
|
||||
h2 {
|
||||
@apply flex flex-row gap-1 items-center justify-start relative top-px;
|
||||
|
||||
> span {
|
||||
@apply text-gray-700;
|
||||
}
|
||||
}
|
||||
|
||||
menu {
|
||||
@apply flex flex-row items-center justify-end gap-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (width >= 96rem) { /* 2xl */
|
||||
main {
|
||||
body#app & {
|
||||
aside {
|
||||
> h1 {
|
||||
@apply h-22;
|
||||
}
|
||||
}
|
||||
article {
|
||||
grid-template-rows: 5.5rem auto;
|
||||
}
|
||||
}
|
||||
|
@ -32,31 +32,58 @@ button,
|
||||
}
|
||||
}
|
||||
|
||||
/* button groups are used with labels/checks as well as regular buttons */
|
||||
.button-group {
|
||||
@apply relative flex flex-row items-center p-0 m-0 h-11 max-h-11;
|
||||
@apply relative flex flex-row items-center p-0 m-0 h-11 max-h-11 rounded-md;
|
||||
box-shadow: 2.5px 2.5px 0 0 var(--color-primary);
|
||||
|
||||
> label {
|
||||
> label,
|
||||
> button {
|
||||
@apply relative flex items-center justify-center h-full pl-3.5 pr-3 cursor-pointer;
|
||||
@apply border-md border-primary font-medium;
|
||||
box-shadow: 1.5px 2.5px 0 0 var(--color-primary);
|
||||
@apply border-md border-primary border-l-0 font-medium rounded-none;
|
||||
transition: background-color 100ms ease-in-out;
|
||||
|
||||
> input[type="radio"] {
|
||||
@apply hidden absolute top-0 left-0 w-0 h-0 max-w-0 max-h-0;
|
||||
&:hover {
|
||||
@apply bg-cyan-300;
|
||||
}
|
||||
|
||||
&:has(input:checked),
|
||||
&:active {
|
||||
@apply bg-cyan-300;
|
||||
box-shadow:
|
||||
inset 2.5px 0 0 0 var(--color-primary),
|
||||
inset 0 0.25rem 0 0 var(--color-cyan-400);
|
||||
left: 0;
|
||||
top: 2.5px;
|
||||
|
||||
+ label {
|
||||
box-shadow:
|
||||
inset 1.5px 0 0 0 var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
@apply rounded-l-md;
|
||||
@apply rounded-l-md border-l-md;
|
||||
|
||||
&:has(input:checked),
|
||||
&:active {
|
||||
box-shadow: inset 0 0.25rem 0 0 var(--color-cyan-400);
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
@apply border-r-md rounded-r-md;
|
||||
}
|
||||
}
|
||||
|
||||
&:has(input:checked) {
|
||||
@apply bg-cyan-300 border-t-2;
|
||||
box-shadow: inset 0 0.25rem 0 0 var(--color-cyan-400);
|
||||
left: 1.5px;
|
||||
top: 2.5px;
|
||||
> label {
|
||||
> input[type="radio"] {
|
||||
@apply hidden absolute top-0 left-0 w-0 h-0 max-w-0 max-h-0;
|
||||
}
|
||||
}
|
||||
|
||||
&:has(> :last-child input:checked),
|
||||
&:has(> :last-child:active) {
|
||||
box-shadow: 1.5px 4.5px 0 -2px var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,89 @@
|
||||
<x-app-layout id="calendar">
|
||||
|
||||
<x-slot name="header">
|
||||
<x-slot name="aside">
|
||||
<h1>
|
||||
{{ __('Calendar') }}
|
||||
</h1>
|
||||
<div class="grow flex flex-col gap-4">
|
||||
<details open>
|
||||
<summary>{{ __('My Calendars') }}</summary>
|
||||
<form id="calendar-toggles"
|
||||
class="content"
|
||||
action="{{ route('calendar.index') }}"
|
||||
method="get">
|
||||
<ul>
|
||||
@foreach ($calendars as $cal)
|
||||
<li>
|
||||
<label class="flex items-center space-x-2">
|
||||
<input type="checkbox"
|
||||
class="calendar-toggle"
|
||||
name="c[]"
|
||||
value="{{ $cal['slug'] }}"
|
||||
style="--checkbox-color: {{ $cal['color'] }}"
|
||||
@checked($cal['visible'])>
|
||||
<span>{{ $cal['name'] }}</span>
|
||||
</label>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
|
||||
{{-- fallback submit button for no-JS environments --}}
|
||||
<noscript>
|
||||
<button type="submit">{{ __('Apply') }}</button>
|
||||
</noscript>
|
||||
</form>
|
||||
</details>
|
||||
</div>
|
||||
<x-calendar.mini>
|
||||
@foreach ($grid['weeks'] as $week)
|
||||
@foreach ($week as $day)
|
||||
<x-calendar.mini-day :day="$day" />
|
||||
@endforeach
|
||||
@endforeach
|
||||
</x-calendar.mini>
|
||||
</x-slot>
|
||||
|
||||
<x-slot name="header">
|
||||
<h2>
|
||||
<strong>{{ $active['month'] }}</strong>
|
||||
<span>{{ $active['year'] }}</span>
|
||||
</h2>
|
||||
<menu>
|
||||
<li>
|
||||
<form class="button-group button-group--primary" method="get" action="/">
|
||||
<x-button.group-button>Day</x-button.group-button>
|
||||
<x-button.group-button>Week</x-button.group-button>
|
||||
<x-button.group-button active="true">Month</x-button.group-button>
|
||||
<x-button.group-button>3-Up</x-button.group-button>
|
||||
<form id="calendar-nav"
|
||||
action="{{ route('calendar.index') }}"
|
||||
method="get"
|
||||
hx-get="{{ route('calendar.index') }}"
|
||||
hx-target="#calendar"
|
||||
hx-select="#calendar"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
hx-include="#calendar-toggles">
|
||||
|
||||
{{-- keep current view (month/week/4day) --}}
|
||||
<input type="hidden" name="view" value="{{ $view }}">
|
||||
<nav class="button-group button-group--primary">
|
||||
<x-button.group-button type="submit" name="date" value="{{ $nav['prev'] }}">
|
||||
<x-icon-chevron-left />
|
||||
</x-button.group-button>
|
||||
<x-button.group-button type="submit" name="date" value="{{ $nav['today'] }}">
|
||||
Today
|
||||
</x-button.group-button>
|
||||
<x-button.group-button type="submit" name="date" value="{{ $nav['next'] }}">
|
||||
<x-icon-chevron-right />
|
||||
</x-button.group-button>
|
||||
</nav>
|
||||
<noscript>
|
||||
{{-- not needed, buttons already submit the form --}}
|
||||
</noscript>
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<form id="calendar-view" class="button-group button-group--primary" method="get" action="/">
|
||||
<x-button.group-input>Day</x-button.group-button>
|
||||
<x-button.group-input>Week</x-button.group-button>
|
||||
<x-button.group-input active="true">Month</x-button.group-button>
|
||||
<x-button.group-input>3-Up</x-button.group-button>
|
||||
</form>
|
||||
<li>
|
||||
<a class="button button--primary" href="{{ route('calendar.create') }}">
|
||||
@ -30,48 +99,7 @@
|
||||
</x-slot>
|
||||
|
||||
<x-slot name="article">
|
||||
|
||||
<aside>
|
||||
<div class="flex flex-col gap-4">
|
||||
<details open>
|
||||
<summary>{{ __('My Calendars') }}</summary>
|
||||
<form id="calendar-toggles"
|
||||
class="content"
|
||||
action="{{ route('calendar.index') }}"
|
||||
method="get">
|
||||
<ul>
|
||||
@foreach ($calendars as $cal)
|
||||
<li>
|
||||
<label class="flex items-center space-x-2">
|
||||
<input type="checkbox"
|
||||
class="calendar-toggle"
|
||||
name="c[]"
|
||||
value="{{ $cal['slug'] }}"
|
||||
style="--checkbox-color: {{ $cal['color'] }}"
|
||||
@checked($cal['visible'])>
|
||||
<span>{{ $cal['name'] }}</span>
|
||||
</label>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
|
||||
{{-- fallback submit button for no-JS environments --}}
|
||||
<noscript>
|
||||
<button type="submit">{{ __('Apply') }}</button>
|
||||
</noscript>
|
||||
</form>
|
||||
</details>
|
||||
</div>
|
||||
<x-calendar.mini>
|
||||
@foreach ($grid['weeks'] as $week)
|
||||
@foreach ($week as $day)
|
||||
<x-calendar.mini-day :day="$day" />
|
||||
@endforeach
|
||||
@endforeach
|
||||
</x-calendar.mini>
|
||||
</aside>
|
||||
|
||||
<x-calendar.full class="month" :grid="$grid" :calendars="$calendars" :events="$events" />
|
||||
|
||||
</x-slot>
|
||||
|
||||
</x-app-layout>
|
||||
|
@ -1,11 +1,10 @@
|
||||
@props([
|
||||
'type' => 'submit',
|
||||
'name' => 'button-group',
|
||||
'value' => '1',
|
||||
'value' => '',
|
||||
'class' => '',
|
||||
'active' => false ])
|
||||
])
|
||||
|
||||
<label class="{{ $class }}">
|
||||
<input type="radio" name="{{ $name }}" value="{{ $value }}" @checked($active)>
|
||||
<button type="{{ $type }}" name="{{ $name }}" value="{{ $value }}" class="{{ $class }}">
|
||||
{{ $slot }}
|
||||
</label>
|
||||
|
10
resources/views/components/button/group-input.blade.php
Normal file
10
resources/views/components/button/group-input.blade.php
Normal file
@ -0,0 +1,10 @@
|
||||
@props([
|
||||
'name' => 'button-group',
|
||||
'value' => '1',
|
||||
'class' => '',
|
||||
'active' => false ])
|
||||
|
||||
<label class="{{ $class }}">
|
||||
<input type="radio" name="{{ $name }}" value="{{ $value }}" @checked($active)>
|
||||
{{ $slot }}
|
||||
</label>
|
@ -14,21 +14,31 @@
|
||||
|
||||
<!-- content -->
|
||||
<main>
|
||||
@isset($header)
|
||||
<header>
|
||||
{{ $header }}
|
||||
</header>
|
||||
|
||||
@isset($aside)
|
||||
<aside>
|
||||
{{ $aside }}
|
||||
</aside>
|
||||
@endisset
|
||||
|
||||
<article {{ $attributes }}>
|
||||
|
||||
@isset($header)
|
||||
<header>
|
||||
{{ $header }}
|
||||
</header>
|
||||
@endisset
|
||||
|
||||
{{ $article ?? $slot }}
|
||||
|
||||
</article>
|
||||
|
||||
</main>
|
||||
|
||||
<!-- messages -->
|
||||
<aside>
|
||||
<figure>
|
||||
@if (session('toast'))
|
||||
<div
|
||||
<figcaption
|
||||
x-data="{ open: true }"
|
||||
x-show="open"
|
||||
x-init="setTimeout(() => open = false, 4000)"
|
||||
@ -36,9 +46,9 @@
|
||||
x-transition.opacity.duration.300ms
|
||||
>
|
||||
{{ session('toast') }}
|
||||
</div>
|
||||
</figcaption>
|
||||
@endif
|
||||
</aside>
|
||||
</figure>
|
||||
|
||||
<!-- modal -->
|
||||
<x-modal />
|
||||
|
Loading…
Reference in New Issue
Block a user