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
|
// get the view and time range
|
||||||
[$view, $range] = $this->resolveRange($request);
|
[$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', []));
|
$visible = collect($request->query('c', []));
|
||||||
|
|
||||||
// load the user's calendars
|
// load the user's calendars
|
||||||
@ -106,10 +111,15 @@ class CalendarController extends Controller
|
|||||||
$payload = [
|
$payload = [
|
||||||
'view' => $view,
|
'view' => $view,
|
||||||
'range' => $range,
|
'range' => $range,
|
||||||
'active' => [
|
'nav' => [
|
||||||
|
'prev' => $prev,
|
||||||
|
'next' => $next,
|
||||||
|
'today' => $today,
|
||||||
|
],
|
||||||
|
'active' => [
|
||||||
'year' => $range['start']->format('Y'),
|
'year' => $range['start']->format('Y'),
|
||||||
'month' => $range['start']->format("F"),
|
'month' => $range['start']->format("F"),
|
||||||
'day' => $range['start']->format("d"),
|
'day' => $range['start']->format("d"),
|
||||||
],
|
],
|
||||||
'calendars' => $calendar_map->map(function ($cal) {
|
'calendars' => $calendar_map->map(function ($cal) {
|
||||||
return [
|
return [
|
||||||
|
@ -22,118 +22,133 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* primary app navigation on the left */
|
/* primary app navigation on the left */
|
||||||
nav {
|
> nav {
|
||||||
@apply w-20 flex flex-col items-center justify-between;
|
@apply w-20 flex flex-col items-center justify-between;
|
||||||
|
|
||||||
/* top items */
|
/* top items */
|
||||||
.top {
|
.top {
|
||||||
@apply flex flex-col items-center pt-6 2xl:pt-8 mt-2px;
|
@apply flex flex-col items-center pt-6 2xl:pt-8 mt-2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* bottom items */
|
/* bottom items */
|
||||||
|
|
||||||
.bottom {
|
.bottom {
|
||||||
@apply pb-6 2xl:pb-8;
|
@apply pb-6 2xl:pb-8;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* app buttons */
|
/* app buttons */
|
||||||
menu {
|
menu {
|
||||||
@apply flex flex-col gap-1 items-center mt-6;
|
@apply flex flex-col gap-1 items-center mt-6;
|
||||||
|
|
||||||
li.app-button {
|
li.app-button {
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@apply flex items-center justify-center p-3 bg-transparent text-black;
|
@apply flex items-center justify-center p-3 bg-transparent text-black;
|
||||||
transition: background-color 100ms ease-in-out;
|
transition: background-color 100ms ease-in-out;
|
||||||
border-radius: 70% 50% 70% 30% / 60% 60% 60% 40%; /* blob 1 */
|
border-radius: 70% 50% 70% 30% / 60% 60% 60% 40%; /* blob 1 */
|
||||||
|
|
||||||
&:hover {
|
|
||||||
@apply bg-gray-200;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-active {
|
|
||||||
@apply bg-cyan-400;
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@apply bg-cyan-500;
|
@apply bg-gray-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
@apply bg-cyan-400;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
@apply bg-cyan-500;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(2) a {
|
&:nth-child(2) a {
|
||||||
border-radius: 70% 30% 30% 70% / 60% 40% 60% 40%; /* blob 2 */
|
border-radius: 70% 30% 30% 70% / 60% 40% 60% 40%; /* blob 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
&:nth-child(3) a {
|
&:nth-child(3) a {
|
||||||
border-radius: 80% 65% 90% 50% / 90% 80% 75% 75%; /* blob 3 */
|
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 {
|
main {
|
||||||
@apply rounded-lg bg-white;
|
@apply rounded-lg bg-white;
|
||||||
|
|
||||||
|
/* app */
|
||||||
body#app & {
|
body#app & {
|
||||||
@apply grid m-2 ml-0;
|
@apply grid grid-cols-1 m-2 ml-0;
|
||||||
grid-template-rows: 5rem auto;
|
|
||||||
|
/* if there's an aside, set the cols */
|
||||||
|
&:has(aside) {
|
||||||
|
grid-template-columns: minmax(20rem, 20dvw) auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* auth screens */
|
||||||
body#auth & {
|
body#auth & {
|
||||||
@apply w-1/2 mx-auto p-8;
|
@apply w-1/2 mx-auto p-8;
|
||||||
min-width: 16rem;
|
min-width: 16rem;
|
||||||
max-width: 40rem;
|
max-width: 40rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* main content title and actions */
|
/* left column */
|
||||||
> header {
|
aside {
|
||||||
@apply grid items-center;
|
@apply flex flex-col col-span-1 px-6 2xl:px-8 pb-8 h-full;
|
||||||
grid-template-columns: minmax(20rem, 20dvw) repeat(3, 1fr);
|
|
||||||
|
|
||||||
h1 {
|
> h1 {
|
||||||
@apply flex items-center pl-6 2xl:pl-8;
|
@apply flex items-center h-20;
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* main content wrapper */
|
/* main content wrapper */
|
||||||
> article {
|
article {
|
||||||
@apply grid w-full;
|
@apply grid grid-cols-1 w-full;
|
||||||
grid-template-columns: minmax(20rem, 20dvw) repeat(3, 1fr);
|
grid-template-rows: 5rem auto;
|
||||||
|
|
||||||
/* left column */
|
/* main content title and actions */
|
||||||
aside {
|
> header {
|
||||||
@apply col-span-1 px-6 2xl:px-8 h-full;
|
@apply flex flex-row items-center justify-between w-full;
|
||||||
}
|
|
||||||
|
|
||||||
/* calendar page defaults */
|
/* if h1 exists it means there's no aside, so force the width from that */
|
||||||
&#calendar {
|
h1 {
|
||||||
|
@apply flex items-center pl-6 2xl:pl-8;
|
||||||
|
width: minmax(20rem, 20dvw);
|
||||||
|
}
|
||||||
|
|
||||||
aside {
|
h2 {
|
||||||
@apply grid pb-6 2xl:pb-8;
|
@apply flex flex-row gap-1 items-center justify-start relative top-px;
|
||||||
grid-template-rows: 1fr min-content;
|
|
||||||
|
> span {
|
||||||
|
@apply text-gray-700;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
menu {
|
||||||
|
@apply flex flex-row items-center justify-end gap-2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (width >= 96rem) { /* 2xl */
|
@media (width >= 96rem) { /* 2xl */
|
||||||
main {
|
main {
|
||||||
body#app & {
|
aside {
|
||||||
|
> h1 {
|
||||||
|
@apply h-22;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
article {
|
||||||
grid-template-rows: 5.5rem auto;
|
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 {
|
.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 relative flex items-center justify-center h-full pl-3.5 pr-3 cursor-pointer;
|
||||||
@apply border-md border-primary font-medium;
|
@apply border-md border-primary border-l-0 font-medium rounded-none;
|
||||||
box-shadow: 1.5px 2.5px 0 0 var(--color-primary);
|
transition: background-color 100ms ease-in-out;
|
||||||
|
|
||||||
> input[type="radio"] {
|
&:hover {
|
||||||
@apply hidden absolute top-0 left-0 w-0 h-0 max-w-0 max-h-0;
|
@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 {
|
&: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 {
|
&:last-child {
|
||||||
@apply border-r-md rounded-r-md;
|
@apply border-r-md rounded-r-md;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:has(input:checked) {
|
> label {
|
||||||
@apply bg-cyan-300 border-t-2;
|
> input[type="radio"] {
|
||||||
box-shadow: inset 0 0.25rem 0 0 var(--color-cyan-400);
|
@apply hidden absolute top-0 left-0 w-0 h-0 max-w-0 max-h-0;
|
||||||
left: 1.5px;
|
|
||||||
top: 2.5px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&: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-app-layout id="calendar">
|
||||||
|
|
||||||
<x-slot name="header">
|
<x-slot name="aside">
|
||||||
<h1>
|
<h1>
|
||||||
{{ __('Calendar') }}
|
{{ __('Calendar') }}
|
||||||
</h1>
|
</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>
|
<h2>
|
||||||
<strong>{{ $active['month'] }}</strong>
|
<strong>{{ $active['month'] }}</strong>
|
||||||
<span>{{ $active['year'] }}</span>
|
<span>{{ $active['year'] }}</span>
|
||||||
</h2>
|
</h2>
|
||||||
<menu>
|
<menu>
|
||||||
<li>
|
<li>
|
||||||
<form class="button-group button-group--primary" method="get" action="/">
|
<form id="calendar-nav"
|
||||||
<x-button.group-button>Day</x-button.group-button>
|
action="{{ route('calendar.index') }}"
|
||||||
<x-button.group-button>Week</x-button.group-button>
|
method="get"
|
||||||
<x-button.group-button active="true">Month</x-button.group-button>
|
hx-get="{{ route('calendar.index') }}"
|
||||||
<x-button.group-button>3-Up</x-button.group-button>
|
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>
|
</form>
|
||||||
<li>
|
<li>
|
||||||
<a class="button button--primary" href="{{ route('calendar.create') }}">
|
<a class="button button--primary" href="{{ route('calendar.create') }}">
|
||||||
@ -30,48 +99,7 @@
|
|||||||
</x-slot>
|
</x-slot>
|
||||||
|
|
||||||
<x-slot name="article">
|
<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-calendar.full class="month" :grid="$grid" :calendars="$calendars" :events="$events" />
|
||||||
|
|
||||||
</x-slot>
|
</x-slot>
|
||||||
|
|
||||||
</x-app-layout>
|
</x-app-layout>
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
@props([
|
@props([
|
||||||
'type' => 'submit',
|
'type' => 'submit',
|
||||||
'name' => 'button-group',
|
'name' => 'button-group',
|
||||||
'value' => '1',
|
'value' => '',
|
||||||
'class' => '',
|
'class' => '',
|
||||||
'active' => false ])
|
])
|
||||||
|
|
||||||
<label class="{{ $class }}">
|
<button type="{{ $type }}" name="{{ $name }}" value="{{ $value }}" class="{{ $class }}">
|
||||||
<input type="radio" name="{{ $name }}" value="{{ $value }}" @checked($active)>
|
|
||||||
{{ $slot }}
|
{{ $slot }}
|
||||||
</label>
|
</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 -->
|
<!-- content -->
|
||||||
<main>
|
<main>
|
||||||
@isset($header)
|
|
||||||
<header>
|
@isset($aside)
|
||||||
{{ $header }}
|
<aside>
|
||||||
</header>
|
{{ $aside }}
|
||||||
|
</aside>
|
||||||
@endisset
|
@endisset
|
||||||
|
|
||||||
<article {{ $attributes }}>
|
<article {{ $attributes }}>
|
||||||
|
|
||||||
|
@isset($header)
|
||||||
|
<header>
|
||||||
|
{{ $header }}
|
||||||
|
</header>
|
||||||
|
@endisset
|
||||||
|
|
||||||
{{ $article ?? $slot }}
|
{{ $article ?? $slot }}
|
||||||
|
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- messages -->
|
<!-- messages -->
|
||||||
<aside>
|
<figure>
|
||||||
@if (session('toast'))
|
@if (session('toast'))
|
||||||
<div
|
<figcaption
|
||||||
x-data="{ open: true }"
|
x-data="{ open: true }"
|
||||||
x-show="open"
|
x-show="open"
|
||||||
x-init="setTimeout(() => open = false, 4000)"
|
x-init="setTimeout(() => open = false, 4000)"
|
||||||
@ -36,9 +46,9 @@
|
|||||||
x-transition.opacity.duration.300ms
|
x-transition.opacity.duration.300ms
|
||||||
>
|
>
|
||||||
{{ session('toast') }}
|
{{ session('toast') }}
|
||||||
</div>
|
</figcaption>
|
||||||
@endif
|
@endif
|
||||||
</aside>
|
</figure>
|
||||||
|
|
||||||
<!-- modal -->
|
<!-- modal -->
|
||||||
<x-modal />
|
<x-modal />
|
||||||
|
Loading…
Reference in New Issue
Block a user