Improves event display on the calendar, gets closer to a v1 of the calendar design, updates guest layout to match theme, adds additional css components for accordion, calendar, checkbox, indicator, and input.
This commit is contained in:
parent
643ac833ba
commit
7efcf5cf55
@ -41,7 +41,8 @@ class CalendarController extends Controller
|
|||||||
'calendars.id',
|
'calendars.id',
|
||||||
'ci.displayname',
|
'ci.displayname',
|
||||||
'ci.calendarcolor',
|
'ci.calendarcolor',
|
||||||
'meta.color as meta_color'
|
'meta.color as meta_color',
|
||||||
|
'meta.color_fg as meta_color_fg'
|
||||||
)
|
)
|
||||||
->join('calendarinstances as ci', 'ci.calendarid', '=', 'calendars.id')
|
->join('calendarinstances as ci', 'ci.calendarid', '=', 'calendars.id')
|
||||||
->leftJoin('calendar_meta as meta', 'meta.calendar_id', '=', 'calendars.id')
|
->leftJoin('calendar_meta as meta', 'meta.calendar_id', '=', 'calendars.id')
|
||||||
@ -63,12 +64,18 @@ class CalendarController extends Controller
|
|||||||
$payload = [
|
$payload = [
|
||||||
'view' => $view,
|
'view' => $view,
|
||||||
'range' => $range,
|
'range' => $range,
|
||||||
|
'active' => [
|
||||||
|
'year' => $range['start']->format('Y'),
|
||||||
|
'month' => $range['start']->format("F"),
|
||||||
|
'day' => $range['start']->format("d"),
|
||||||
|
],
|
||||||
'calendars' => $calendars->keyBy('id')->map(function ($cal) {
|
'calendars' => $calendars->keyBy('id')->map(function ($cal) {
|
||||||
return [
|
return [
|
||||||
'id' => $cal->id,
|
'id' => $cal->id,
|
||||||
'name' => $cal->displayname,
|
'name' => $cal->displayname,
|
||||||
'color' => $cal->meta_color ?? $cal->calendarcolor ?? '#999',
|
'color' => $cal->meta_color ?? $cal->calendarcolor ?? '#1a1a1a', // clean this up @todo
|
||||||
'on' => true, // default to visible; the UI can toggle this
|
'color_fg' => $cal->meta_color_fg ?? '#ffffff', // clean this up
|
||||||
|
'on' => true, // default to visible; the UI can toggle this
|
||||||
];
|
];
|
||||||
}),
|
}),
|
||||||
'events' => $events->map(function ($e) { // just the events map
|
'events' => $events->map(function ($e) { // just the events map
|
||||||
@ -83,7 +90,9 @@ class CalendarController extends Controller
|
|||||||
'calendar_id' => $e->calendarid,
|
'calendar_id' => $e->calendarid,
|
||||||
'title' => $e->meta->title ?? '(no title)',
|
'title' => $e->meta->title ?? '(no title)',
|
||||||
'start' => $start->format('c'),
|
'start' => $start->format('c'),
|
||||||
|
'start_ui' => $start->format('g:ia'),
|
||||||
'end' => optional($end)->format('c'),
|
'end' => optional($end)->format('c'),
|
||||||
|
'end_ui' => optional($end)->format('g:ia')
|
||||||
];
|
];
|
||||||
}),
|
}),
|
||||||
'grid' => $grid,
|
'grid' => $grid,
|
||||||
@ -129,7 +138,8 @@ class CalendarController extends Controller
|
|||||||
// update calendar meta
|
// update calendar meta
|
||||||
$instance->meta()->create([
|
$instance->meta()->create([
|
||||||
'calendar_id' => $instanceId,
|
'calendar_id' => $instanceId,
|
||||||
'color' => $data['color'] ?? null,
|
'color' => $data['color'] ?? '#1a1a1a',
|
||||||
|
'color_fg' => contrast_text_color($data['color'] ?? '#1a1a1a'),
|
||||||
'created_at' => now(),
|
'created_at' => now(),
|
||||||
'updated_at' => now(),
|
'updated_at' => now(),
|
||||||
]);
|
]);
|
||||||
@ -212,8 +222,9 @@ class CalendarController extends Controller
|
|||||||
|
|
||||||
// update calendar meta (our table)
|
// update calendar meta (our table)
|
||||||
$calendar->meta()->updateOrCreate([], [
|
$calendar->meta()->updateOrCreate([], [
|
||||||
'color' => $data['color'] ?? null]
|
'color' => $data['color'] ?? '#1a1a1a',
|
||||||
);
|
'color_fg' => contrast_text_color($data['color'] ?? '#1a1a1a')
|
||||||
|
]);
|
||||||
|
|
||||||
return redirect()
|
return redirect()
|
||||||
->route('calendar.show', $calendar)
|
->route('calendar.show', $calendar)
|
||||||
@ -313,7 +324,9 @@ class CalendarController extends Controller
|
|||||||
'calendar_id' => $ev->calendarid,
|
'calendar_id' => $ev->calendarid,
|
||||||
'title' => $ev->meta->title ?? '(no title)',
|
'title' => $ev->meta->title ?? '(no title)',
|
||||||
'start' => $start->format('c'),
|
'start' => $start->format('c'),
|
||||||
'end' => $end->format('c'),
|
'start_ui' => $start->format('g:ia'),
|
||||||
|
'end' => optional($end)->format('c'),
|
||||||
|
'end_ui' => optional($end)->format('g:ia')
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ class CalendarMeta extends Model
|
|||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'calendar_id',
|
'calendar_id',
|
||||||
'color',
|
'color',
|
||||||
|
'color_fg',
|
||||||
'created_at',
|
'created_at',
|
||||||
'edited_at',
|
'edited_at',
|
||||||
];
|
];
|
||||||
|
67
app/Support/helpers.php
Normal file
67
app/Support/helpers.php
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
if (! function_exists('format_event_url')) {
|
||||||
|
/**
|
||||||
|
* format an event url with a given calendar ID and event ID
|
||||||
|
*/
|
||||||
|
function format_event_url(string $eid, string $cid): string
|
||||||
|
{
|
||||||
|
return 'calendar/'.$cid.'/event/'.$eid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! function_exists('contrast_text_color')) {
|
||||||
|
/**
|
||||||
|
* Choose an accessible foreground (#fff or #000) against a HEX background.
|
||||||
|
*
|
||||||
|
* Rule set:
|
||||||
|
* 1. Calculate WCAG contrast ratios for both black and white.
|
||||||
|
* 2. Prefer the colour with the *higher* ratio.
|
||||||
|
* 3. Override: if black wins but ratio < 5.5 AND white > 4, use white.
|
||||||
|
*
|
||||||
|
* @param string $hex Background colour (3- or 6-digit hex, with or without #)
|
||||||
|
* @param string $light Return value for “white” (default '#ffffff')
|
||||||
|
* @param string $dark Return value for “black” (default '#000000')
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function contrast_text_color(string $hex, string $light = '#ffffff', string $dark = '#000000'): string
|
||||||
|
{
|
||||||
|
// --- normalise ----------------------------------------------------
|
||||||
|
$hex = ltrim($hex, '#');
|
||||||
|
if (strlen($hex) === 3) { // #abc → #aabbcc
|
||||||
|
$hex = preg_replace('/./', '$0$0', $hex);
|
||||||
|
}
|
||||||
|
|
||||||
|
[$r, $g, $b] = [
|
||||||
|
hexdec(substr($hex, 0, 2)) / 255,
|
||||||
|
hexdec(substr($hex, 2, 2)) / 255,
|
||||||
|
hexdec(substr($hex, 4, 2)) / 255,
|
||||||
|
];
|
||||||
|
|
||||||
|
// --- convert sRGB → linear RGB -----------------------------------
|
||||||
|
$linear = function (float $c): float {
|
||||||
|
return $c <= 0.04045 ? $c / 12.92 : pow(($c + 0.055) / 1.055, 2.4);
|
||||||
|
};
|
||||||
|
|
||||||
|
$R = $linear($r);
|
||||||
|
$G = $linear($g);
|
||||||
|
$B = $linear($b);
|
||||||
|
|
||||||
|
// --- relative luminance (ITU-R BT.709) ----------------------------
|
||||||
|
$L_bg = 0.2126 * $R + 0.7152 * $G + 0.0722 * $B; // 0–1
|
||||||
|
|
||||||
|
// --- contrast ratios vs black (L=0) and white (L=1) ---------------
|
||||||
|
$contrast_black = ($L_bg + 0.05) / 0.05; // bg lighter than black
|
||||||
|
$contrast_white = 1.05 / ($L_bg + 0.05); // white vs bg
|
||||||
|
|
||||||
|
// --- pick the winner ---------------------------------------------
|
||||||
|
$useDark = $contrast_black >= $contrast_white;
|
||||||
|
|
||||||
|
// override rule if dark is true but white "looks better"
|
||||||
|
if ($useDark && $contrast_black < 5.5 && $contrast_white > 4) {
|
||||||
|
$useDark = false; // switch to white
|
||||||
|
}
|
||||||
|
|
||||||
|
return $useDark ? $dark : $light;
|
||||||
|
}
|
||||||
|
}
|
@ -28,7 +28,10 @@
|
|||||||
"App\\": "app/",
|
"App\\": "app/",
|
||||||
"Database\\Factories\\": "database/factories/",
|
"Database\\Factories\\": "database/factories/",
|
||||||
"Database\\Seeders\\": "database/seeders/"
|
"Database\\Seeders\\": "database/seeders/"
|
||||||
}
|
},
|
||||||
|
"files": [
|
||||||
|
"app/Support/helpers.php"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"autoload-dev": {
|
"autoload-dev": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
@ -9,9 +9,10 @@ return new class extends Migration
|
|||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('calendar_meta', function (Blueprint $table) {
|
Schema::create('calendar_meta', function (Blueprint $table) {
|
||||||
$table->unsignedInteger('calendar_id')->primary(); // FK = PK
|
$table->unsignedInteger('calendar_id')->primary(); // FK = PK
|
||||||
$table->string('title')->nullable(); // UI override
|
$table->string('title')->nullable(); // ui override
|
||||||
$table->string('color', 7)->nullable(); // e.g. #FFAA00
|
$table->string('color', 7)->nullable(); // bg color
|
||||||
|
$table->string('color_fg', 7)->nullable(); // fg color
|
||||||
$table->boolean('is_shared')->default(false);
|
$table->boolean('is_shared')->default(false);
|
||||||
$table->json('settings')->nullable(); // arbitrary JSON
|
$table->json('settings')->nullable(); // arbitrary JSON
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
|
@ -5,7 +5,12 @@
|
|||||||
/** kithkin */
|
/** kithkin */
|
||||||
@import './etc/layout.css';
|
@import './etc/layout.css';
|
||||||
@import './etc/type.css';
|
@import './etc/type.css';
|
||||||
|
@import './lib/accordion.css';
|
||||||
@import './lib/button.css';
|
@import './lib/button.css';
|
||||||
|
@import './lib/calendar.css';
|
||||||
|
@import './lib/checkbox.css';
|
||||||
|
@import './lib/indicator.css';
|
||||||
|
@import './lib/input.css';
|
||||||
@import './lib/mini.css';
|
@import './lib/mini.css';
|
||||||
|
|
||||||
/** plugins */
|
/** plugins */
|
||||||
|
@ -91,14 +91,23 @@ main {
|
|||||||
|
|
||||||
/* main content title and actions */
|
/* main content title and actions */
|
||||||
> header {
|
> header {
|
||||||
@apply flex flex-row items-center justify-between px-6 2xl:px-8;
|
@apply grid items-center;
|
||||||
|
grid-template-columns: minmax(20rem, 20dvw) repeat(3, 1fr);
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
@apply h-12 max-h-12;
|
@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 {
|
menu {
|
||||||
@apply flex flex-row items-center justify-end gap-2 h-12 max-h-12;
|
@apply col-span-2 flex flex-row items-center justify-end gap-2 pr-6 2xl:pr-8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +134,7 @@ main {
|
|||||||
@media (width >= 96rem) { /* 2xl */
|
@media (width >= 96rem) { /* 2xl */
|
||||||
main {
|
main {
|
||||||
body#app & {
|
body#app & {
|
||||||
grid-template-rows: 6rem auto;
|
grid-template-rows: 5.5rem auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,25 @@
|
|||||||
--color-cyan-400: oklch(92.6% 0.117 195.31);
|
--color-cyan-400: oklch(92.6% 0.117 195.31);
|
||||||
--color-cyan-500: oklch(90.54% 0.155 194.76); /* 00ffff */
|
--color-cyan-500: oklch(90.54% 0.155 194.76); /* 00ffff */
|
||||||
--color-cyan-550: oklch(82% 0.2812 194.769); /* 00e3e3 */
|
--color-cyan-550: oklch(82% 0.2812 194.769); /* 00e3e3 */
|
||||||
|
--color-cyan-600: oklch(80.43% 0.137 194.76); /* todo below */
|
||||||
|
--color-cyan-700: oklch(70.28% 0.12 194.76);
|
||||||
|
--color-cyan-800: oklch(59.15% 0.101 194.76);
|
||||||
|
--color-cyan-900: oklch(49.05% 0.084 194.76);
|
||||||
|
--color-cyan-950: oklch(43.96% 0.075 194.76);
|
||||||
|
--color-magenta-50: oklch(96.93% 0.027 325.87);
|
||||||
|
--color-magenta-100: oklch(93.76% 0.056 326.06);
|
||||||
|
--color-magenta-200: oklch(87.58% 0.117 326.54);
|
||||||
|
--color-magenta-300: oklch(81.59% 0.181 327.09);
|
||||||
|
--color-magenta-400: oklch(75.66% 0.251 327.72);
|
||||||
|
--color-magenta-500: oklch(70.17% 0.322 328.37);
|
||||||
|
--color-magenta-550: oklch(0.666 0.3061 328.36); /* ee00ee */
|
||||||
|
--color-magenta-600: oklch(62.55% 0.287 328.37);
|
||||||
|
--color-magenta-700: oklch(55.14% 0.253 328.37);
|
||||||
|
--color-magenta-800: oklch(47.69% 0.219 328.37);
|
||||||
|
--color-magenta-900: oklch(40.42% 0.186 328.37);
|
||||||
|
--color-magenta-950: oklch(36.79% 0.169 328.37);
|
||||||
|
|
||||||
--border-width-1.5: 1.5px;
|
--border-width-md: 1.5px;
|
||||||
|
|
||||||
--radius-xs: 0.25rem;
|
--radius-xs: 0.25rem;
|
||||||
--radius-sm: 0.375rem;
|
--radius-sm: 0.375rem;
|
||||||
@ -36,12 +53,18 @@
|
|||||||
--radius-4xl: 3rem;
|
--radius-4xl: 3rem;
|
||||||
--radius-blob: 80% 65% 90% 50% / 90% 80% 75% 75%;
|
--radius-blob: 80% 65% 90% 50% / 90% 80% 75% 75%;
|
||||||
|
|
||||||
--shadow-drop: 2.5px 2.5px 0 0 var(--color-primary);
|
--shadow-drop: 2.5px 2.5px 0 0 var(--color-primary);
|
||||||
|
--shadow-input: inset 0 0.25rem 0 0 var(--color-gray-100);
|
||||||
|
|
||||||
|
--spacing-md: 1.5px;
|
||||||
--spacing-2px: 2px;
|
--spacing-2px: 2px;
|
||||||
|
|
||||||
|
--text-2xs: 0.625rem;
|
||||||
|
--text-2xs--line-height: 1.2;
|
||||||
|
--text-2xl: 1.75rem;
|
||||||
|
--text-2xl--line-height: 1.333;
|
||||||
--text-3xl: 2rem;
|
--text-3xl: 2rem;
|
||||||
--text-3xl--line-height: calc(2.25 / 1.875);
|
--text-3xl--line-height: 1.2;
|
||||||
--text-4xl: 3rem;
|
--text-4xl: 3rem;
|
||||||
--text-4xl--line-height: 1;
|
--text-4xl--line-height: 1;
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,25 @@
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* app name */
|
||||||
h1 {
|
h1 {
|
||||||
@apply font-serif text-3xl font-extrabold leading-tight;
|
@apply font-serif text-3xl font-extrabold leading-tight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* page header */
|
||||||
|
h2 {
|
||||||
|
@apply font-serif text-2xl font-extrabold leading-tight text-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* links */
|
||||||
|
a {
|
||||||
|
&.text {
|
||||||
|
@apply underline decoration-inherit underline-offset-2 text-magenta-600;
|
||||||
|
text-decoration-thickness: 1.5px;
|
||||||
|
transition: color 125ms ease-in-out;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
@apply text-magenta-700;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
42
resources/css/lib/accordion.css
Normal file
42
resources/css/lib/accordion.css
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
details {
|
||||||
|
|
||||||
|
summary {
|
||||||
|
@apply relative flex items-center cursor-pointer list-none h-8 font-semibold z-0;
|
||||||
|
|
||||||
|
&::-webkit-details-marker {
|
||||||
|
@apply hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
@apply block absolute top-0 left-0 -ml-3 -mt-1 bg-transparent rounded-md;
|
||||||
|
content: '';
|
||||||
|
height: calc(100% + 0.5rem);
|
||||||
|
transition: background-color 100ms ease-in-out;
|
||||||
|
width: calc(100% + 1.5rem);
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
@apply block w-8 h-8 absolute right-0 top-0 bg-no-repeat bg-center;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
|
||||||
|
content: '';
|
||||||
|
transition: rotate 100ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
&::before {
|
||||||
|
@apply bg-gray-100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[open] {
|
||||||
|
summary::after {
|
||||||
|
@apply rotate-180;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .content {
|
||||||
|
@apply mt-2;
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,13 @@
|
|||||||
button,
|
button,
|
||||||
.button {
|
.button {
|
||||||
@apply relative inline-flex items-center cursor-pointer gap-2 rounded-md h-11 px-4 text-lg font-medium;
|
@apply relative inline-flex items-center cursor-pointer gap-2 rounded-md h-11 px-4 text-lg font-medium;
|
||||||
transition: background-color 100ms ease-in-out;
|
transition: background-color 125ms ease-in-out;
|
||||||
--button-border: var(--color-primary);
|
--button-border: var(--color-primary);
|
||||||
--button-accent: var(--color-primary-hover);
|
--button-accent: var(--color-primary-hover);
|
||||||
|
|
||||||
&.button--primary {
|
&.button--primary {
|
||||||
@apply bg-cyan-300;
|
@apply bg-cyan-300 border-md border-solid;
|
||||||
border: 1.5px solid var(--button-border);
|
border-color: var(--button-border);
|
||||||
box-shadow: 2.5px 2.5px 0 0 var(--button-border);
|
box-shadow: 2.5px 2.5px 0 0 var(--button-border);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@ -31,3 +31,32 @@ button,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
@apply relative flex flex-row items-center p-0 m-0 h-11 max-h-11;
|
||||||
|
|
||||||
|
> label {
|
||||||
|
@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);
|
||||||
|
|
||||||
|
> input[type="radio"] {
|
||||||
|
@apply hidden absolute top-0 left-0 w-0 h-0 max-w-0 max-h-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
@apply rounded-l-md;
|
||||||
|
}
|
||||||
|
|
||||||
|
&: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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
64
resources/css/lib/calendar.css
Normal file
64
resources/css/lib/calendar.css
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
.calendar {
|
||||||
|
@apply grid col-span-3 pr-6 2xl:pr-8 pb-6 2xl:pb-8 pt-2;
|
||||||
|
grid-template-rows: 2rem 1fr;
|
||||||
|
|
||||||
|
hgroup {
|
||||||
|
@apply grid grid-cols-7 w-full gap-1;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
@apply uppercase text-right pr-4 font-bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ol {
|
||||||
|
@apply grid grid-cols-7 w-full gap-1;
|
||||||
|
contain: paint;
|
||||||
|
grid-auto-rows: 1fr;
|
||||||
|
|
||||||
|
li {
|
||||||
|
@apply relative px-1 pt-8 border-t-md border-primary;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
@apply absolute top-0 right-px w-auto h-8 flex items-center justify-end pr-4 text-sm font-medium;
|
||||||
|
content: attr(data-day-number);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.day--outside {
|
||||||
|
@apply bg-gray-50 text-gray-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.day--today {
|
||||||
|
@apply bg-cyan-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(-n+7) {
|
||||||
|
@apply border-t-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
@apply rounded-br-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event {
|
||||||
|
@apply flex items-center text-xs gap-1 px-1 py-px font-medium truncate rounded-sm bg-transparent;
|
||||||
|
transition: background-color 125ms ease-in-out;
|
||||||
|
|
||||||
|
.indicator {
|
||||||
|
--indicator-bg: var(--event-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
@apply grow;
|
||||||
|
}
|
||||||
|
|
||||||
|
time {
|
||||||
|
@apply text-2xs;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: color-mix(in srgb, var(--event-color) 25%, #fff 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
resources/css/lib/checkbox.css
Normal file
16
resources/css/lib/checkbox.css
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
input[type="checkbox"] {
|
||||||
|
@apply border-md rounded-sm w-5 h-5 ring-0;
|
||||||
|
transition: border 150ms ease-in-out,
|
||||||
|
outline 150ms ease-in-out,
|
||||||
|
background 150ms ease-in-out,
|
||||||
|
box-shadow 200ms ease-out;
|
||||||
|
color: var(--checkbox-color);
|
||||||
|
border-color: var(--checkbox-color);
|
||||||
|
--checkbox-color: var(--color-primary);
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: 2px solid transparent;
|
||||||
|
outline-offset: 2px;
|
||||||
|
box-shadow: 0 0 0 2px #fff, 0 0 0 4px var(--checkbox-color), var(--tw-shadow);
|
||||||
|
}
|
||||||
|
}
|
5
resources/css/lib/indicator.css
Normal file
5
resources/css/lib/indicator.css
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
i.indicator {
|
||||||
|
@apply inline-flex w-2.5 h-2.5 min-w-2.5 min-h-2.5 rounded-full font-normal;
|
||||||
|
background-color: var(--indicator-bg);
|
||||||
|
--indicator-bg: var(--color-magenta-500); /* default color */
|
||||||
|
}
|
9
resources/css/lib/input.css
Normal file
9
resources/css/lib/input.css
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
input[type="email"],
|
||||||
|
input[type="text"],
|
||||||
|
input[type="password"],
|
||||||
|
input[type="search"] {
|
||||||
|
@apply border-md border-gray-800 bg-white rounded-md shadow-input;
|
||||||
|
@apply focus:border-primary focus:ring-2 focus:ring-offset-2 focus:ring-cyan-600;
|
||||||
|
transition: box-shadow 125ms ease-in-out,
|
||||||
|
border-color 125ms ease-in-out;
|
||||||
|
}
|
@ -12,10 +12,10 @@
|
|||||||
|
|
||||||
/* days wrapper */
|
/* days wrapper */
|
||||||
figure {
|
figure {
|
||||||
@apply border-1.5 border-primary shadow-drop rounded-md;
|
@apply border-md border-primary shadow-drop rounded-md;
|
||||||
|
|
||||||
/* weekdays */
|
/* weekdays */
|
||||||
figcaption {
|
hgroup {
|
||||||
@apply grid grid-cols-7 p-2 pt-3 pb-0;
|
@apply grid grid-cols-7 p-2 pt-3 pb-0;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
@ -55,7 +55,7 @@
|
|||||||
|
|
||||||
&.day--with-events {
|
&.day--with-events {
|
||||||
&::after {
|
&::after {
|
||||||
@apply absolute bottom-0 left-1/2 -translate-x-1/2 h-1 rounded-full w-4 bg-yellow-500;
|
@apply absolute bottom-0 left-1/2 -translate-x-1/2 h-1 rounded-full w-4 bg-magenta-500;
|
||||||
content: '';
|
content: '';
|
||||||
}
|
}
|
||||||
&[data-event-count='1']::after {
|
&[data-event-count='1']::after {
|
||||||
|
1
resources/svg/icons/chevron-down.svg
Normal file
1
resources/svg/icons/chevron-down.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down-icon lucide-chevron-down"><path d="m6 9 6 6 6-6"/></svg>
|
After Width: | Height: | Size: 271 B |
1
resources/svg/icons/chevron-left.svg
Normal file
1
resources/svg/icons/chevron-left.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-left-icon lucide-chevron-left"><path d="m15 18-6-6 6-6"/></svg>
|
After Width: | Height: | Size: 273 B |
1
resources/svg/icons/chevron-right.svg
Normal file
1
resources/svg/icons/chevron-right.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-right-icon lucide-chevron-right"><path d="m9 18 6-6-6-6"/></svg>
|
After Width: | Height: | Size: 274 B |
1
resources/svg/icons/chevron-up.svg
Normal file
1
resources/svg/icons/chevron-up.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-up-icon lucide-chevron-up"><path d="m18 15-6-6-6 6"/></svg>
|
After Width: | Height: | Size: 269 B |
@ -26,15 +26,15 @@
|
|||||||
|
|
||||||
<!-- Remember Me -->
|
<!-- Remember Me -->
|
||||||
<div class="block mt-4">
|
<div class="block mt-4">
|
||||||
<label for="remember_me" class="inline-flex items-center">
|
<label for="remember_me" class="inline-flex items-center gap-2">
|
||||||
<input id="remember_me" type="checkbox" class="rounded-sm border-gray-300 text-indigo-600 shadow-xs focus:ring-indigo-500" name="remember">
|
<input id="remember_me" type="checkbox" name="remember">
|
||||||
<span class="ms-2 text-sm text-gray-600">{{ __('Remember me') }}</span>
|
<span>{{ __('Remember me') }}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center justify-between mt-4 gap-4">
|
<div class="flex items-center justify-between mt-4 gap-4">
|
||||||
@if (Route::has('password.request'))
|
@if (Route::has('password.request'))
|
||||||
<a class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-hidden focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" href="{{ route('password.request') }}">
|
<a href="{{ route('password.request') }}" href="text">
|
||||||
{{ __('Forgot your password?') }}
|
{{ __('Forgot your password?') }}
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
|
@ -3,7 +3,18 @@
|
|||||||
<h1>
|
<h1>
|
||||||
{{ __('Calendar') }}
|
{{ __('Calendar') }}
|
||||||
</h1>
|
</h1>
|
||||||
|
<h2>
|
||||||
|
<strong>{{ $active['month'] }}</strong>
|
||||||
|
<span>{{ $active['year'] }}</span>
|
||||||
|
</h2>
|
||||||
<menu>
|
<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>
|
||||||
<li>
|
<li>
|
||||||
<a class="button button--primary" href="{{ route('calendar.create') }}">
|
<a class="button button--primary" href="{{ route('calendar.create') }}">
|
||||||
<x-icon-plus-circle /> Create
|
<x-icon-plus-circle /> Create
|
||||||
@ -18,19 +29,23 @@
|
|||||||
</x-slot>
|
</x-slot>
|
||||||
<x-slot name="article">
|
<x-slot name="article">
|
||||||
<aside>
|
<aside>
|
||||||
<div>
|
<div class="flex flex-col gap-4">
|
||||||
@foreach ($calendars as $cal)
|
<details open>
|
||||||
<label class="flex items-center space-x-2">
|
<summary>{{ __('My Calendars') }}</summary>
|
||||||
<input type="checkbox"
|
<ul class="content">
|
||||||
wire:model="visibleCalendars"
|
@foreach ($calendars as $cal)
|
||||||
value="{{ $cal['id'] }}"
|
<li>
|
||||||
checked>
|
<label class="flex items-center space-x-2">
|
||||||
<span class="w-3 h-3 rounded-sm" style="background: {{ $cal['color'] }}"></span>
|
<input type="checkbox"
|
||||||
<span>{{ $cal['name'] }}</span>
|
value="{{ $cal['id'] }}"
|
||||||
</label>
|
style="--checkbox-color: {{ $cal['color'] }}"
|
||||||
@endforeach
|
checked>
|
||||||
|
<span>{{ $cal['name'] }}</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
@endforeach
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<x-calendar.mini>
|
<x-calendar.mini>
|
||||||
@foreach ($grid['weeks'] as $week)
|
@foreach ($grid['weeks'] as $week)
|
||||||
@foreach ($week as $day)
|
@foreach ($week as $day)
|
||||||
@ -39,5 +54,6 @@
|
|||||||
@endforeach
|
@endforeach
|
||||||
</x-calendar.mini>
|
</x-calendar.mini>
|
||||||
</aside>
|
</aside>
|
||||||
|
<x-calendar.full class="month" :grid="$grid" :calendars="$calendars" />
|
||||||
</x-slot>
|
</x-slot>
|
||||||
</x-app-layout>
|
</x-app-layout>
|
||||||
|
11
resources/views/components/button/group-button.blade.php
Normal file
11
resources/views/components/button/group-button.blade.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
@props([
|
||||||
|
'type' => 'submit',
|
||||||
|
'name' => 'button-group',
|
||||||
|
'value' => '1',
|
||||||
|
'class' => '',
|
||||||
|
'active' => false ])
|
||||||
|
|
||||||
|
<label class="{{ $class }}">
|
||||||
|
<input type="radio" name="{{ $name }}" value="{{ $value }}" @checked($active)>
|
||||||
|
{{ $slot }}
|
||||||
|
</label>
|
26
resources/views/components/calendar/day.blade.php
Normal file
26
resources/views/components/calendar/day.blade.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
@props([
|
||||||
|
'day', // required
|
||||||
|
'calendars' => [], // calendar palette keyed by id
|
||||||
|
])
|
||||||
|
|
||||||
|
<li
|
||||||
|
data-day-number="{{ $day['label'] }}"
|
||||||
|
data-event-count="{{ count($day['events'] ?? []) }}"
|
||||||
|
@class([
|
||||||
|
'day',
|
||||||
|
'day--with-events' => !empty($day['events']),
|
||||||
|
'day--current' => $day['in_month'],
|
||||||
|
'day--outside' => !$day['in_month'],
|
||||||
|
'day--today' => $day['is_today'],
|
||||||
|
])>
|
||||||
|
@foreach ($day['events'] as $event)
|
||||||
|
@php
|
||||||
|
$bg = $calendars[(string) $event['calendar_id']]['color'] ?? '#999';
|
||||||
|
@endphp
|
||||||
|
<a class="event" href="{{ format_event_url($event['id'], $event['calendar_id']) }}" style="--event-color: {{ $bg }}">
|
||||||
|
<i class="indicator" aria-label="Calendar indicator"></i>
|
||||||
|
<span class="title">{{ $event['title'] }}</span>
|
||||||
|
<time>{{ $event['start_ui'] }}</time>
|
||||||
|
</a>
|
||||||
|
@endforeach
|
||||||
|
</li>
|
24
resources/views/components/calendar/full.blade.php
Normal file
24
resources/views/components/calendar/full.blade.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
@props([
|
||||||
|
'grid' => ['weeks' => []],
|
||||||
|
'calendars' => [],
|
||||||
|
'class' => ''
|
||||||
|
])
|
||||||
|
|
||||||
|
<section class="calendar {{ $class }}">
|
||||||
|
<hgroup>
|
||||||
|
<span>Mon</span>
|
||||||
|
<span>Tue</span>
|
||||||
|
<span>Wed</span>
|
||||||
|
<span>Thu</span>
|
||||||
|
<span>Fri</span>
|
||||||
|
<span>Sat</span>
|
||||||
|
<span>Sun</span>
|
||||||
|
</hgroup>
|
||||||
|
<ol data-weeks="{{ count($grid['weeks']) }}">
|
||||||
|
@foreach ($grid['weeks'] as $week)
|
||||||
|
@foreach ($week as $day)
|
||||||
|
<x-calendar.day :day="$day" :calendars="$calendars" />
|
||||||
|
@endforeach
|
||||||
|
@endforeach
|
||||||
|
</ol>
|
||||||
|
</section>
|
@ -6,7 +6,7 @@
|
|||||||
<menu>Controls</menu>
|
<menu>Controls</menu>
|
||||||
</header>
|
</header>
|
||||||
<figure>
|
<figure>
|
||||||
<figcaption>
|
<hgroup>
|
||||||
<span>U</span>
|
<span>U</span>
|
||||||
<span>M</span>
|
<span>M</span>
|
||||||
<span>T</span>
|
<span>T</span>
|
||||||
@ -14,7 +14,7 @@
|
|||||||
<span>R</span>
|
<span>R</span>
|
||||||
<span>F</span>
|
<span>F</span>
|
||||||
<span>S</span>
|
<span>S</span>
|
||||||
</figcaption>
|
</hgroup>
|
||||||
<form action="/" method="get">
|
<form action="/" method="get">
|
||||||
{{ $slot }}
|
{{ $slot }}
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
@props(['disabled' => false])
|
@props(['disabled' => false])
|
||||||
|
|
||||||
<input @disabled($disabled) {{ $attributes->merge(['class' => 'border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-xs']) }}>
|
<input @disabled($disabled) {{ $attributes->merge(['class' => '']) }}>
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
<h1>{{ config('app.name', 'Kithkin') }}</h1>
|
<h1>{{ config('app.name', 'Kithkin') }}</h1>
|
||||||
</a>
|
</a>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main class="border-md border-primary shadow-drop">
|
||||||
{{ $slot }}
|
{{ $slot }}
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
Loading…
Reference in New Issue
Block a user