diff --git a/.storybook/theme.css b/.storybook/theme.css index 36e4eb449ea..6695be3418c 100644 --- a/.storybook/theme.css +++ b/.storybook/theme.css @@ -31,6 +31,18 @@ manually copy them here. --theme-color-ui-accent-bg: oklch(0.457 0.24 277.023); --theme-color-ui-accent-text: var(--theme-color-ui-accent-bg); --theme-color-switch-bg: var(--theme-color-ui-accent-bg); + --theme-color-chart-1: oklch(0.585 0.233 277.117); + --theme-color-chart-2: oklch(0.274 0.006 286.033); + --theme-color-chart-3: oklch(0.768 0.233 130.85); + --theme-color-chart-4: oklch(0.87 0.065 274.039); + --theme-color-chart-1-label-bg: oklch(0.511 0.262 276.966); + --theme-color-chart-2-label-bg: transparent; + --theme-color-chart-3-label-bg: oklch(0.648 0.2 131.684); + --theme-color-chart-4-label-bg: oklch(0.673 0.182 276.935); + --theme-color-chart-1-legend: oklch(0.585 0.233 277.117); + --theme-color-chart-2-legend: oklch(0.274 0.006 286.033); + --theme-color-chart-3-legend: oklch(0.768 0.233 130.85); + --theme-color-chart-4-legend: oklch(0.785 0.115 274.713); --z-index-below: -1; --z-index-above: 1; @@ -45,6 +57,18 @@ manually copy them here. --theme-color-content-bg: oklch(0.21 0.006 285.885); --theme-color-content-border: oklch(0.141 0.005 285.823); --theme-color-ui-accent-text: oklch(0.673 0.182 276.935); + --theme-color-chart-1: oklch(0.511 0.262 276.966); + --theme-color-chart-2: oklch(0.87 0.065 274.039); + --theme-color-chart-3: oklch(0.768 0.233 130.85); + --theme-color-chart-4: oklch(0.141 0.005 285.823); + --theme-color-chart-1-label-bg: oklch(0.457 0.24 277.023); + --theme-color-chart-2-label-bg: oklch(0.673 0.182 276.935); + --theme-color-chart-3-label-bg: oklch(0.648 0.2 131.684); + --theme-color-chart-4-label-bg: oklch(0.37 0.013 285.805); + --theme-color-chart-1-legend: oklch(0.511 0.262 276.966); + --theme-color-chart-2-legend: oklch(0.87 0.065 274.039); + --theme-color-chart-3-legend: oklch(0.768 0.233 130.85); + --theme-color-chart-4-legend: oklch(0.141 0.005 285.823); } } diff --git a/resources/css/components/forms/charts.css b/resources/css/components/forms/charts.css new file mode 100644 index 00000000000..3577635f611 --- /dev/null +++ b/resources/css/components/forms/charts.css @@ -0,0 +1,343 @@ +/* GROUP CHARTS / PIE CHART +=================================================== */ +.pie-chart-figure { + display: grid; + grid-template-columns: 0.6fr 1fr; + @apply gap-4 p-6; +} + +.pie-chart-legend { + @apply pt-4; +} + +.pie-chart-legend__list { + @apply grid gap-2.25 text-xs text-gray-700 dark:text-gray-50; +} + +.pie-chart-legend__item { + @apply flex items-center gap-2.25; +} + +.pie-chart-legend__value { + @apply min-w-7 text-end text-[0.785rem] font-medium; +} + +.pie-chart-legend__swatch { + @apply size-2.5 shrink-0 rounded-full; +} + +.pie-chart-legend__swatch--1 { @apply bg-chart-1-legend; } +.pie-chart-legend__swatch--2 { @apply bg-chart-2-legend; } +.pie-chart-legend__swatch--3 { @apply bg-chart-3-legend; } +.pie-chart-legend__swatch--4 { @apply bg-chart-4-legend; } + +.pie-chart-legend__link { + @apply contents cursor-pointer text-inherit; +} + +.pie-chart { + --label-radius: 30%; + + position: relative; + width: 9rem; + aspect-ratio: 1 / 1; + + /* Slice sizes as unitless numbers (percent of circle). */ + --end1: calc(var(--1) * 1%); + --end2: calc((var(--1) + var(--2)) * 1%); + --end3: calc((var(--1) + var(--2) + var(--3)) * 1%); + --end4: 100%; + + /* Midpoint angle for each slice (100% = 360deg). */ + --angle1: calc(var(--1) * 3.6deg / 2); + --angle2: calc((var(--1) + var(--2) / 2) * 3.6deg); + --angle3: calc((var(--1) + var(--2) + var(--3) / 2) * 3.6deg); + --angle4: calc((var(--1) + var(--2) + var(--3) + var(--4) / 2) * 3.6deg); + + &:hover { + .pie-chart__label { + opacity: unset; + } + } +} + +.pie-chart__disc { + position: relative; + width: 100%; + height: 100%; + border-radius: 50%; + background-image: conic-gradient( + var(--color-chart-1) 0% var(--end1), + var(--color-chart-2) var(--end1) var(--end2), + var(--color-chart-3) var(--end2) var(--end3), + var(--color-chart-4) var(--end3) var(--end4) + ); +} + +.pie-chart__label { + --angle: 0deg; + opacity: 0; + transition: opacity 0.2s ease-in 0s; + + padding: 0.075rem 0.15rem; + border-radius: 0.25rem; + + position: absolute; + left: calc(50% + var(--label-radius) * sin(var(--angle))); + top: calc(50% - var(--label-radius) * cos(var(--angle))); + transform: translate(-50%, -50%); + + @apply text-xs font-semibold tabular-nums text-white pointer-events-none; +} + +.pie-chart__label--1 { --angle: var(--angle1); background-color: var(--color-chart-1-label-bg); } +.pie-chart__label--2 { --angle: var(--angle2); background-color: var(--color-chart-2-label-bg); } +.pie-chart__label--3 { --angle: var(--angle3); background-color: var(--color-chart-3-label-bg); } +/* Kick the radius out a bit because the label segment is smaller, and it might overlap with the preceding segment. */ +.pie-chart__label--4 { --angle: var(--angle4); background-color: var(--color-chart-4-label-bg); --label-radius: 39%; } + + +/* GROUP CHARTS / PIE CHART / MODIFIERS +=================================================== */ +.pie-chart--other-segment { + .pie-chart__label, + &.pie-chart:hover .pie-chart__label { + opacity: 0; + } + + .pie-chart__label--4, + &.pie-chart:hover .pie-chart__label--4 { + opacity: 1; + border: 2px solid black; + } + + .pie-chart__disc { + background-image: conic-gradient( + hsl(from var(--color-chart-1) h s l / 0.1) 0% var(--end1), + hsl(from var(--color-chart-2) h s l / 0.1) var(--end1) var(--end2), + hsl(from var(--color-chart-3) h s l / 0.1) var(--end2) var(--end3), + var(--color-chart-4) var(--end3) var(--end4) + ); + } +} + + + +/* GROUP CHARTS / IMAGE PIE CHART +=================================================== */ +.image-pie-chart-figure { + display: grid; + grid-template-columns: 0.6fr 1fr; + @apply gap-4 p-6; +} + +.image-pie-chart-legend { + @apply pt-4; +} + +.image-pie-chart { + --label-radius: 30%; + + position: relative; + width: 9rem; + aspect-ratio: 1 / 1; + + /* Midpoint angle for each slice (100% = 360deg). */ + --angle1: calc(var(--1) * 3.6deg / 2); + --angle2: calc((var(--1) + var(--2) / 2) * 3.6deg); + + &:hover { + .image-pie-chart__label { + opacity: unset; + } + } +} + +.image-pie-chart__disc { + position: relative; + width: 100%; + height: 100%; + border-radius: 50%; + overflow: hidden; + + @apply bg-gray-200 dark:bg-gray-700; +} + +.image-pie-chart__slice { + position: absolute; + inset: 0; + border-radius: 50%; + background-image: var(--image); + background-size: cover; + background-position: center; +} + +.image-pie-chart__slice--1 { + --slice-size: calc(var(--1) * 3.6deg); + + -webkit-mask-image: conic-gradient(from 0deg, #000 0deg var(--slice-size), transparent var(--slice-size)); + mask-image: conic-gradient(from 0deg, #000 0deg var(--slice-size), transparent var(--slice-size)); +} + +.image-pie-chart__slice--2 { + --slice-start: calc(var(--1) * 3.6deg); + --slice-size: calc(var(--2) * 3.6deg); + + -webkit-mask-image: conic-gradient(from var(--slice-start), #000 0deg var(--slice-size), transparent var(--slice-size)); + mask-image: conic-gradient(from var(--slice-start), #000 0deg var(--slice-size), transparent var(--slice-size)); +} + +.image-pie-chart__label { + --angle: 0deg; + opacity: 0; + transition: opacity 0.2s ease-in 0s; + + padding: 0.075rem 0.15rem; + border-radius: 0.25rem; + + position: absolute; + left: calc(50% + var(--label-radius) * sin(var(--angle))); + top: calc(50% - var(--label-radius) * cos(var(--angle))); + transform: translate(-50%, -50%); + + @apply text-xs font-medium tabular-nums text-white pointer-events-none; +} + +.image-pie-chart__label--1 { --angle: var(--angle1); background-color: hsl(0 0% 0% / 0.55); } +.image-pie-chart__label--2 { --angle: var(--angle2); background-color: hsl(0 0% 0% / 0.55); } + + + +/* GROUP CHARTS / VERTICAL BAR CHART +=================================================== */ +.vertical-bar-chart-figure { + display: grid; + height: 100%; + @apply py-3 px-3; +} + +.vertical-bar-chart { + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: 0.1rem; + height: 100%; + list-style: none; + padding: 0; +} + +.vertical-bar-chart__bar { + display: grid; + flex: 1; + grid-template-rows: 1fr auto; + align-items: end; + justify-items: center; + gap: 0.5rem; + min-width: 0; + height: 100%; +} + +.vertical-bar-chart__plot { + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: center; + width: 100%; + height: 100%; + min-height: 0; +} + +.vertical-bar-chart__value { + @apply mb-1.5 text-[0.785rem] font-medium tabular-nums text-gray-800 dark:text-gray-100; +} + +.vertical-bar-chart__fill { + flex: 0 0 calc(var(--value) / var(--max-value) * 100%); + width: 100%; + max-width: 2rem; + border-radius: 0.25rem 0.25rem 0 0; + background-color: var(--color-chart-1); +} + +.vertical-bar-chart__scale-label { + @apply min-w-8 rounded-md border border-gray-200 shadow-ui-xs px-1.5 py-0.5 text-center text-[0.785rem] font-medium tabular-nums text-gray-700 dark:border-gray-700 dark:text-gray-300; +} + + + +/* GROUP CHARTS / SUMMARY BAR CHART / ICON STROKES +=================================================== */ +/* Note: This is used to style the checkbox icons in the summary bar chart depending on the background contrast. */ +.summary-bar-chart__icon-stroke { + color: var(--chart-tick-color); +} + +.summary-bar-chart__icon-stroke path:not(:first-child) { + stroke: white; +} + +@supports (color: contrast-color(red)) { + .dark .summary-bar-chart__icon-stroke path:not(:first-child) { + stroke: contrast-color(var(--chart-tick-color)); + } +} + +.summary-bar-chart__icon-stroke--1 { --chart-tick-color: var(--color-chart-1-legend); } +.summary-bar-chart__icon-stroke--2 { --chart-tick-color: var(--color-chart-2-legend); } +.summary-bar-chart__icon-stroke--3 { --chart-tick-color: var(--color-chart-3-legend); } +.summary-bar-chart__icon-stroke--4 { --chart-tick-color: var(--color-chart-4-legend); } + + + +/* GROUP CHARTS / DISPLAY MODE (% or count) +=================================================== */ +.chart-metric::before { + content: attr(data-percent); +} + +[data-submission-summary][data-chart-metric="count"] .chart-metric::before { + content: attr(data-count); +} + + + +/* GROUP CHARTS / ANIMATIONS / PIE CHART +=================================================== */ +@media (prefers-reduced-motion: no-preference) { + /* These animations are triggered when switching chart types */ + @property --pie-reveal { + syntax: ''; + inherits: false; + initial-value: 0deg; + } + + @keyframes pie-chart-reveal { + from { --pie-reveal: 0deg; } + to { --pie-reveal: 360deg; } + } + + @keyframes bar-chart-reveal { + from { transform: scaleX(0); } + to { transform: scaleX(1); } + } + + .content-card:has([data-submission-summary]) { + [data-ui-card]:has([data-ui-toggle-group-interacted]) { + .pie-chart__disc, + .image-pie-chart__disc { + -webkit-mask-image: conic-gradient(from 0deg, #000 0deg, #000 var(--pie-reveal), transparent var(--pie-reveal)); + mask-image: conic-gradient(from 0deg, #000 0deg, #000 var(--pie-reveal), transparent var(--pie-reveal)); + --pie-reveal: 0deg; + animation: pie-chart-reveal 0.75s cubic-bezier(0.33, 1, 0.68, 1) forwards; + } + + .summary-bar-chart__fill { + transform-origin: left center; + } + + .summary-bar-chart__fill { + animation: bar-chart-reveal 0.75s cubic-bezier(0.33, 1, 0.68, 1) forwards; + } + } + } +} \ No newline at end of file diff --git a/resources/css/cp.css b/resources/css/cp.css index cb32123428f..91dd3b5d8da 100644 --- a/resources/css/cp.css +++ b/resources/css/cp.css @@ -18,6 +18,7 @@ @import './components/notifications.css'; @import './components/page-tree.css'; @import './components/forms.css'; +@import './components/forms/charts.css'; @import './components/pagination.css'; @import './components/popover.css'; @import './components/preview.css'; diff --git a/resources/css/ui.css b/resources/css/ui.css index 16816ecbe18..3594899c94b 100644 --- a/resources/css/ui.css +++ b/resources/css/ui.css @@ -30,6 +30,18 @@ --color-ui-accent-bg: var(--theme-color-ui-accent-bg); --color-ui-accent-text: var(--theme-color-ui-accent-text); --color-switch-bg: var(--theme-color-switch-bg); + --color-chart-1: var(--theme-color-chart-1); + --color-chart-2: var(--theme-color-chart-2); + --color-chart-3: var(--theme-color-chart-3); + --color-chart-4: var(--theme-color-chart-4); + --color-chart-1-label-bg: var(--theme-color-chart-1-label-bg); + --color-chart-2-label-bg: var(--theme-color-chart-2-label-bg); + --color-chart-3-label-bg: var(--theme-color-chart-3-label-bg); + --color-chart-4-label-bg: var(--theme-color-chart-4-label-bg); + --color-chart-1-legend: var(--theme-color-chart-1-legend); + --color-chart-2-legend: var(--theme-color-chart-2-legend); + --color-chart-3-legend: var(--theme-color-chart-3-legend); + --color-chart-4-legend: var(--theme-color-chart-4-legend); /* Custom color shades for all Tailwind color families */ --color-slate-150: oklch(0.9485 0.01 251.702); diff --git a/resources/js/components/forms/SubmissionListing.vue b/resources/js/components/forms/SubmissionListing.vue index db3004806d5..109643d533a 100644 --- a/resources/js/components/forms/SubmissionListing.vue +++ b/resources/js/components/forms/SubmissionListing.vue @@ -9,8 +9,17 @@ :sort-direction="sortDirection" :preferences-prefix="preferencesPrefix" :filters="filters" + :show-results="view === 'entries'" push-query > + + + + diff --git a/resources/js/components/ui/Listing/Filters.vue b/resources/js/components/ui/Listing/Filters.vue index 5b6a15edc2a..d80404a125b 100644 --- a/resources/js/components/ui/Listing/Filters.vue +++ b/resources/js/components/ui/Listing/Filters.vue @@ -131,18 +131,24 @@ function handleStackClosed() {