Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
f5a3a57
wip
jackmcdade Sep 26, 2025
30dcc5d
Wire up CreateEntryButton
jackmcdade Sep 26, 2025
c0001c7
Make sure we don't get a row full of days outside the currently displ…
jackmcdade Sep 26, 2025
d4fc72d
Fix line clamp
jackmcdade Sep 26, 2025
50be588
fix more indicator
jackmcdade Sep 26, 2025
e31f51b
Remove redundant API Call on Component Mount
jackmcdade Sep 26, 2025
da67395
Better dark mode bg
jackmcdade Sep 26, 2025
2b1404e
Add keyboard navigation
jackmcdade Sep 26, 2025
8413714
Remove incorrect entry count badge
jackmcdade Sep 26, 2025
e93f78d
Initial mobile layout
jackmcdade Sep 26, 2025
efef0c1
Mobile indicators of entries on any given day.
jackmcdade Sep 26, 2025
cf909e3
Adjust math so less entries = narrower bar indicator
jackmcdade Sep 26, 2025
20ad04b
Remove keyboard binding in favor of just standard tab controls which …
jackmcdade Sep 26, 2025
d68cd74
Show entries on a given selected date on small screens
jackmcdade Sep 26, 2025
e9dcc15
Adds Week View
jackmcdade Sep 27, 2025
b9ec79e
Better "today" cell state
jackmcdade Sep 27, 2025
3bda9bd
wip drag & drop entries
jackmcdade Sep 29, 2025
879ce0b
wip hourly drop state
jackmcdade Sep 29, 2025
0708c1a
Refactor and restyle a bit
jackmcdade Sep 29, 2025
e34133c
Prevent entries flashing back to their original position on save.
jackmcdade Sep 29, 2025
741e7da
Refactor into smaller components for readability/maintainability
jackmcdade Sep 29, 2025
8c59b71
Bring back 8am day state position
jackmcdade Sep 29, 2025
f0fa6d1
Improve dark mode
jackmcdade Sep 29, 2025
9214f42
Calendar suggestions / objective - correct day spacing and text contrast
JayGeorge Sep 30, 2025
c7ddfb5
Calendar suggestions / subjective suggestions
JayGeorge Sep 30, 2025
03d625b
Calendar suggestions / add a dot for current day
JayGeorge Sep 30, 2025
7e13588
Calendar suggestions / make colors ui-accent instead
JayGeorge Sep 30, 2025
9ae4fec
Calendar suggestions / compress rows without events
JayGeorge Sep 30, 2025
912a19b
Calendar / fix multiple entries on the week view by adding an overflo…
JayGeorge Sep 30, 2025
8342943
Calendar / week / suggestion to make rows smaller
JayGeorge Sep 30, 2025
30eef3e
Calendar / month / show all entries with overflow scroll, same as wee…
JayGeorge Sep 30, 2025
5fddc71
move css to inline tw + data attr
jackmcdade Sep 30, 2025
d61938e
Switch this to a max-height media query
jackmcdade Sep 30, 2025
05196ff
Click the CalendarHeader to change the month and year
jackmcdade Sep 30, 2025
65d5769
Merge branch 'master' into collection-calendar
jasonvarga Oct 8, 2025
6ed9a73
add dated prop to controller
jasonvarga Oct 8, 2025
096be70
Fix field conditions not applying in filters
jasonvarga Oct 9, 2025
54335d4
Adjust field filter logic
jasonvarga Oct 9, 2025
e22675c
Merge branch 'date-filter' into collection-calendar
jasonvarga Oct 9, 2025
e078281
only get entries for that period
jasonvarga Oct 9, 2025
30e775d
The date range for getting entries should include those few days of t…
jasonvarga Oct 9, 2025
7fb5709
move in preparation to becoming util
jasonvarga Oct 9, 2025
f0b34ec
make it a utility file. there was nothing composable about it
jasonvarga Oct 9, 2025
71317f2
Merge branch 'master' into collection-calendar
jasonvarga Oct 9, 2025
f595117
remove unused stuff
jasonvarga Oct 9, 2025
bddbec9
un-wangjangle existing props
jasonvarga Oct 9, 2025
ba7d75a
dont need customTrigger. if you dont pass a slot, it'll use what you …
jasonvarga Oct 9, 2025
c390ac6
unwrap template - just put v-if on the slot
jasonvarga Oct 9, 2025
5330ea3
Merge branch 'master' into collection-calendar
jasonvarga Oct 9, 2025
1ad7bd2
use inertia link/redirect
jasonvarga Oct 9, 2025
da39244
Create entry button sends the utc datetime to the form
jasonvarga Oct 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions packages/ui/src/Combobox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
FocusScope
} from 'reka-ui';
import { computed, nextTick, ref, useAttrs, useTemplateRef, watch } from 'vue';
import { omit } from 'lodash-es';
import Button from './Button/Button.vue';
import Icon from './Icon/Icon.vue';
import Badge from './Badge.vue';
Expand Down Expand Up @@ -64,7 +65,8 @@ const triggerClasses = cva({
subtle: 'bg-transparent hover:bg-gray-400/10 text-gray-500 hover:text-gray-700 border-none dark:text-gray-300 dark:hover:bg-white/15 dark:hover:text-gray-200 focus-within:focus-outline',
},
size: {
lg: 'px-6 h-12 text-base rounded-lg',
xl: 'px-5 h-12 text-lg rounded-lg',
lg: 'px-4 h-12 text-base rounded-lg',
base: 'px-4 h-10 text-sm rounded-lg',
sm: 'px-3 h-8 text-[0.8125rem] rounded-lg',
xs: 'px-2 h-6 text-xs rounded-md',
Expand Down Expand Up @@ -281,10 +283,10 @@ defineExpose({
class="cursor-pointer"
data-ui-combobox
ignore-filter
v-bind="attrs"
v-bind="omit(attrs, ['class'])"
>
<ComboboxAnchor :class="[$attrs.class]" data-ui-combobox-anchor>
<ComboboxTrigger as="div" ref="trigger" :class="triggerClasses" @keydown.enter="openDropdown" @keydown.space="openDropdown" data-ui-combobox-trigger>
<ComboboxAnchor data-ui-combobox-anchor>
<ComboboxTrigger as="div" ref="trigger" :class="[triggerClasses, $attrs.class]" @keydown.enter="openDropdown" @keydown.space="openDropdown" data-ui-combobox-trigger>
<div class="flex-1 min-w-0">
<ComboboxInput
v-if="searchable && (dropdownOpen || !modelValue || (multiple && placeholder))"
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/src/Popover.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const props = defineProps({

const popoverContentClasses = cva({
base: [
'rounded-xl w-64 bg-white dark:bg-gray-800 outline-hidden overflow-hidden',
'rounded-xl w-64 bg-white dark:bg-gray-800 outline-hidden',
'border border-gray-200 dark:border-white/10 dark:border-b-0 shadow-lg',
'duration-100 will-change-[transform,opacity]',
'data-[state=open]:animate-in data-[state=closed]:animate-out',
Expand Down Expand Up @@ -66,7 +66,7 @@ function updateOpen(value) {
<PopoverClose as-child>
<slot name="close" v-bind="slotProps" />
</PopoverClose>
<PopoverArrow v-if="arrow" class="fill-white stroke-gray-300" />
<PopoverArrow v-if="arrow" class="fill-white dark:fill-gray-800 stroke-gray-300 dark:stroke-gray-700" />
</PopoverContent>
</PopoverPortal>
</PopoverRoot>
Expand Down
2 changes: 2 additions & 0 deletions resources/js/bootstrap/components.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Browser from '../components/assets/Browser/Browser.vue';
import UpdatesBadge from '../components/UpdatesBadge.vue';
import FullscreenHeader from '../components/publish/FullscreenHeader.vue';
import FieldMeta from '../components/publish/FieldMeta.vue';
import CollectionCalendar from '../components/entries/CollectionCalendar.vue';
import EntryPublishForm from '../components/entries/PublishForm.vue';
import TermPublishForm from '../components/terms/PublishForm.vue';
import UserPublishForm from '../components/users/PublishForm.vue';
Expand Down Expand Up @@ -54,6 +55,7 @@ export default function registerGlobalComponents(app) {
app.component('publish-field-meta', FieldMeta);
app.component('publish-field-fullscreen-header', FullscreenHeader);

app.component('CollectionCalendar', CollectionCalendar);
app.component('EntryPublishForm', EntryPublishForm);
app.component('TermPublishForm', TermPublishForm);
app.component('UserPublishForm', UserPublishForm);
Expand Down
39 changes: 39 additions & 0 deletions resources/js/components/entries/CalendarEntry.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<template>
<Link
:href="entry.edit_url"
:key="entry.id"
class="text-2xs @3xl:text-xs px-2 border-s-2 rounded-e-sm cursor-pointer flex flex-col"
:class="entryClasses"
draggable="true"
@dragstart="handleDragStart"
>
<span class="line-clamp-2">
{{ entry.title }}
</span>
<span class="hidden @4xl:block text-2xs text-gray-400 dark:text-gray-400">
{{ formatTime(entry.date?.date || entry.date) }}
</span>
</Link>
</template>

<script setup>
import { computed } from 'vue';
import { formatTime } from '@/util/calendar.js';
import { Link } from '@inertiajs/vue3';

const props = defineProps({
entry: { type: Object, required: true }
});

const emit = defineEmits(['dragstart']);

const entryClasses = computed(() => ({
'border-green-500 hover:bg-green-50': props.entry.status === 'published',
'border-gray-300 hover:bg-gray-50': props.entry.status === 'draft',
'border-purple-500 hover:bg-purple-50': props.entry.status === 'scheduled'
}));

const handleDragStart = (event) => {
emit('dragstart', event, props.entry);
};
</script>
207 changes: 207 additions & 0 deletions resources/js/components/entries/CalendarMonthView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
<script setup>
import { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell } from 'reka-ui';
import CalendarEntry from './CalendarEntry.vue';
import CreateEntryButton from './CreateEntryButton.vue';
import { formatDateString, isToday, getCreateUrlDateParam } from '@/util/calendar.js';

const props = defineProps({
weekDays: { type: Array, required: true },
grid: { type: Array, required: true },
entries: { type: Array, required: true },
pendingDateChanges: { type: Map, required: true },
selectedDate: { type: Object, default: null },
dragOverTarget: { type: Object, default: null },
createUrl: { type: String, required: true },
blueprints: { type: Array, default: () => [] }
});

const emit = defineEmits(['select-date', 'entry-dragstart', 'drag-over', 'drag-enter', 'drag-leave', 'drop']);

const isCurrentDay = (dayIndex) => {
const today = new Date();
const currentDayName = today.toLocaleDateString('en-US', {weekday: 'long'});

// Find the index of today's day name in the weekDays array
const todayIndex = props.weekDays.findIndex(day =>
day.toLowerCase() === currentDayName.toLowerCase()
);

return dayIndex === todayIndex;
};

const getEntriesForDate = (date) => {
const dateStr = formatDateString(date);
return props.entries.filter(entry => {
// Check if this entry has a pending date change
if (props.pendingDateChanges.has(entry.id)) {
const newDate = props.pendingDateChanges.get(entry.id);
return newDate.toISOString().split('T')[0] === dateStr;
}

const entryDate = new Date(entry.date?.date || entry.date);
return entryDate.toISOString().split('T')[0] === dateStr;
});
};

const weekHasEntries = (weekDates) => {
return weekDates.some(date => getEntriesForDate(date).length > 0);
};

const cellClasses = (weekDate, monthValue) => ({
'bg-gray-100 dark:bg-gray-800 ring-1 ring-gray-200 dark:ring-gray-700': weekDate.month !== monthValue.month,
'bg-white dark:bg-gray-900': weekDate.month === monthValue.month,
'bg-ui-accent/10! border border-ui-accent!': isToday(weekDate),
'border-2 border-blue-400 bg-blue-50 dark:bg-blue-900/20': isDragOverDate(weekDate)
});

const dateNumberClasses = (weekDate, selected, today, outsideView) => ({
'text-gray-400 dark:text-gray-600': outsideView,
'text-gray-900 dark:text-white': !outsideView,
'text-white bg-blue-600': props.selectedDate && props.selectedDate.toString() === weekDate.toString(),
'text-ui-accent': today
});

const entryStatusClasses = (status) => ({
'bg-green-500': status === 'published',
'bg-gray-300': status === 'draft',
'bg-purple-500': status === 'scheduled'
});

const isDragOverDate = (date) => {
return props.dragOverTarget && props.dragOverTarget.toString() === date.toString();
};

const selectDate = (date) => {
emit('select-date', date);
};

const handleEntryDragStart = (event, entry) => {
emit('entry-dragstart', event, entry);
};

const handleDragOver = (event) => {
emit('drag-over', event);
};

const handleDragEnter = (event, target) => {
emit('drag-enter', event, target);
};

const handleDragLeave = (event) => {
emit('drag-leave', event);
};

const handleDrop = (event, targetDate) => {
emit('drop', event, targetDate);
};
</script>

<template>
<CalendarGrid class="w-full border-collapse">
<CalendarGridHead>
<CalendarGridRow class="grid grid-cols-7 gap-3 mb-2">
<CalendarHeadCell
v-for="(day, index) in weekDays"
:key="day"
class="p-2 text-center font-medium text-sm text-gray-700 dark:text-gray-400 bg-gray-200/75 dark:bg-gray-900/75 rounded-lg"
>
<div class="flex items-center justify-center gap-1">
<div
v-if="isCurrentDay(index)"
class="w-1.5 h-1.5 mr-1 bg-ui-accent rounded-full"
></div>
<span class="@4xl:hidden">{{ day.slice(0, 2) }}</span>
<span class="hidden @4xl:block">{{ day }}</span>
</div>
</CalendarHeadCell>
</CalendarGridRow>
</CalendarGridHead>

<CalendarGridBody class="space-y-3 calendar-grid">
<template v-for="month in grid" :key="month.value.toString()">
<CalendarGridRow
v-for="(weekDates, weekIndex) in month.rows.filter(weekDates =>
weekDates.some(date => date.month === month.value.month)
)"
:key="`weekDate-${weekIndex}`"
:data-week-has-entries="weekHasEntries(weekDates)"
class="grid grid-cols-7 gap-3"
>
<CalendarCell
v-for="weekDate in weekDates"
:key="weekDate.toString()"
:date="weekDate"
class="aspect-square p-2 rounded-xl ring ring-gray-200 dark:ring-gray-700 shadow-ui-sm group relative"
:class="cellClasses(weekDate, month.value)"
@dragover="handleDragOver"
@dragenter="handleDragEnter($event, weekDate)"
@dragleave="handleDragLeave"
@drop="handleDrop($event, weekDate)"
>
<CalendarCellTrigger
:day="weekDate"
:month="month.value"
class="w-full h-full flex flex-col items-center justify-center @3xl:items-start @3xl:justify-start"
v-slot="{ dayValue, selected, today, outsideView }"
@click="selectDate(weekDate)"
>
<!-- Date number -->
<div
class="text-sm mb-1 rounded-full size-6 flex items-center justify-center"
v-text="dayValue"
:class="dateNumberClasses(weekDate, selected, today, outsideView)"
/>

<!-- Mobile entry indicators -->
<div class="@3xl:hidden w-full" v-if="getEntriesForDate(weekDate).length > 0">
<div class="flex h-1 rounded-full overflow-hidden items-center justify-center">
<div
v-for="(entry, index) in getEntriesForDate(weekDate).slice(0, 4)"
:key="entry.id"
class="h-full first:rounded-s-full last:rounded-e-full"
:class="entryStatusClasses(entry.status)"
style="width: 25%"
/>
</div>
</div>

<!-- Desktop entries -->
<div class="space-y-1.5 flex-1 overflow-scroll h-full w-full hidden @3xl:block">
<CalendarEntry
v-for="entry in getEntriesForDate(weekDate)"
:key="entry.id"
:entry="entry"
@dragstart="handleEntryDragStart"
/>
</div>
</CalendarCellTrigger>

<!-- Create entry button (shows on hover) -->
<div class="absolute top-1 right-1 opacity-0 group-hover:opacity-100 transition-opacity hidden @3xl:block">
<CreateEntryButton
:params="{ values: { date: getCreateUrlDateParam(weekDate) } }"
:blueprints="blueprints"
variant="subtle"
size="sm"
>
<template #trigger="{ create }">
<ui-button icon="plus" size="sm" variant="subtle" @click="create" />
</template>
</CreateEntryButton>
</div>
</CalendarCell>
</CalendarGridRow>
</template>
</CalendarGridBody>
</CalendarGrid>
</template>

<style scoped>
@media (max-height: 999px) {
.calendar-grid {
tr:not([data-week-has-entries="true"]) td {
aspect-ratio: 2 / 1;
}
}
}
</style>
32 changes: 32 additions & 0 deletions resources/js/components/entries/CalendarWeekEntry.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<template>
<a
:href="entry.edit_url"
:key="entry.id"
class="block text-xs p-1 rounded-r border-l-2 mb-1 cursor-pointer hover:shadow-sm"
:class="entryClasses"
draggable="true"
@dragstart="handleDragStart"
>
<div class="font-medium line-clamp-2">{{ entry.title }}</div>
</a>
</template>

<script setup>
import { computed } from 'vue';

const props = defineProps({
entry: { type: Object, required: true }
});

const emit = defineEmits(['dragstart']);

const entryClasses = computed(() => ({
'border-green-500 bg-green-50 dark:bg-green-900/20': props.entry.status === 'published',
'border-gray-300 bg-gray-50 dark:bg-gray-800': props.entry.status === 'draft',
'border-purple-500 bg-purple-50 dark:bg-purple-900/20': props.entry.status === 'scheduled'
}));

const handleDragStart = (event) => {
emit('dragstart', event, props.entry);
};
</script>
Loading