mirror of
https://github.com/Lissy93/personal-security-checklist.git
synced 2024-12-26 07:49:25 -05:00
Finishes progress component, improves styling for homepage, adds theme support, adds ignore option for table, starts working on filters for table, and more
This commit is contained in:
parent
fd639567dd
commit
5d665df3d4
@ -35,6 +35,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@builder.io/qwik": "^1.1.4",
|
||||
"chart.js": "^4.4.1",
|
||||
"marked": "^12.0.0",
|
||||
"progressbar.js": "^1.1.1",
|
||||
"sharp": "^0.33.2"
|
||||
|
@ -109,6 +109,11 @@ const getSvgPath = (icon: string) => {
|
||||
vb: "0 0 512 512",
|
||||
path: "M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z",
|
||||
};
|
||||
case 'filters':
|
||||
return {
|
||||
vb: "0 0 512 512",
|
||||
path: "M3.9 54.9C10.5 40.9 24.5 32 40 32H472c15.5 0 29.5 8.9 36.1 22.9s4.6 30.5-5.2 42.5L320 320.9V448c0 12.1-6.8 23.2-17.7 28.6s-23.8 4.3-33.5-3l-64-48c-8.1-6-12.8-15.5-12.8-25.6V320.9L9 97.3C-.7 85.4-2.8 68.8 3.9 54.9z",
|
||||
};
|
||||
default:
|
||||
return { vb: "", path: "" }; // Default path or a placeholder icon
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import Icon from "../core/icon";
|
||||
|
||||
export default component$(() => {
|
||||
return (
|
||||
<div class="hero bg-base-200 bg-opacity-25 mb-16">
|
||||
<div class="hero bg-front mb-16 shadow-sm">
|
||||
<div class="hero-content text-center">
|
||||
<div class="max-w-2xl flex flex-col place-items-center">
|
||||
<p>The Ultimate</p>
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { component$ } from "@builder.io/qwik";
|
||||
|
||||
import Icon from "~/components/core/icon";
|
||||
import type { Priority, Section } from '../../types/PSC';
|
||||
import { marked } from "marked";
|
||||
import { useLocalStorage } from "~/hooks/useLocalStorage";
|
||||
|
||||
export default component$((props: { section: Section }) => {
|
||||
|
||||
const STORAGE_KEY = 'PSC_PROGRESS';
|
||||
const [value, setValue] = useLocalStorage(STORAGE_KEY, {});
|
||||
const [completed, setCompleted] = useLocalStorage('PSC_PROGRESS', {});
|
||||
const [ignored, setIgnored] = useLocalStorage('PSC_IGNORED', {});
|
||||
|
||||
const getBadgeClass = (priority: Priority, precedeClass: string = '') => {
|
||||
switch (priority.toLocaleLowerCase()) {
|
||||
@ -30,12 +31,62 @@ export default component$((props: { section: Section }) => {
|
||||
return marked.parse(text || '', { async: false }) as string || '';
|
||||
};
|
||||
|
||||
const isChecked = (point: string) => {
|
||||
const pointId = generateId(point);
|
||||
return value.value[pointId] || false;
|
||||
const isIgnored = (pointId: string) => {
|
||||
return ignored.value[pointId] || false;
|
||||
};
|
||||
|
||||
|
||||
const isChecked = (pointId: string) => {
|
||||
if (isIgnored(pointId)) return false;
|
||||
return completed.value[pointId] || false;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
<div class="collapse rounded-none">
|
||||
<input type="checkbox" />
|
||||
<div class="collapse-title flex justify-end font-bold">
|
||||
<button class="btn btn-sm hover:bg-primary"><Icon width={16} height={16} icon="filters"/>Filters</button>
|
||||
</div>
|
||||
<div class="collapse-content flex flex-wrap justify-between bg-base-100 rounded px-4 pt-1 !pb-1">
|
||||
{/* Filter by completion */}
|
||||
<div class="flex justify-end items-center gap-1">
|
||||
<p class="font-bold text-sm">Show</p>
|
||||
<label class="p-2 rounded hover:bg-front transition-all cursor-pointer flex gap-2">
|
||||
<span class="text-sm">All</span>
|
||||
<input type="radio" name="show-all" class="radio radio-sm checked:radio-info" checked />
|
||||
</label>
|
||||
<label class="p-2 rounded hover:bg-front transition-all cursor-pointer flex gap-2">
|
||||
<span class="text-sm">Remaining</span>
|
||||
<input type="radio" name="show-remaining" class="radio radio-sm checked:radio-error" />
|
||||
</label>
|
||||
<label class="p-2 rounded hover:bg-front transition-all cursor-pointer flex gap-2">
|
||||
<span class="text-sm">Completed</span>
|
||||
<input type="radio" name="show-completed" class="radio radio-sm checked:radio-success" />
|
||||
</label>
|
||||
</div>
|
||||
{/* Filter by level */}
|
||||
<div class="flex justify-end items-center gap-1">
|
||||
<p class="font-bold text-sm">Filter</p>
|
||||
<label class="p-2 rounded hover:bg-front transition-all cursor-pointer flex gap-2">
|
||||
<span class="text-sm">Basic</span>
|
||||
<input type="checkbox" checked class="checkbox checkbox-sm checked:checkbox-success" />
|
||||
</label>
|
||||
<label class="p-2 rounded hover:bg-front transition-all cursor-pointer flex gap-2">
|
||||
<span class="text-sm">Optional</span>
|
||||
<input type="checkbox" checked class="checkbox checkbox-sm checked:checkbox-warning" />
|
||||
</label>
|
||||
<label class="p-2 rounded hover:bg-front transition-all cursor-pointer flex gap-2">
|
||||
<span class="text-sm">Advanced</span>
|
||||
<input type="checkbox" checked class="checkbox checkbox-sm checked:checkbox-error" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -46,36 +97,66 @@ export default component$((props: { section: Section }) => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{props.section?.checklist.map((item, index) => (
|
||||
<tr key={index} class={`rounded-sm hover:bg-opacity-5 hover:bg-${getBadgeClass(item.priority)}`}>
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
id={generateId(item.point)}
|
||||
checked={isChecked(item.point)}
|
||||
onClick$={() => {
|
||||
const id = item.point.toLowerCase().replace(/ /g, '-');
|
||||
const data = value.value;
|
||||
data[id] = !data[id];
|
||||
setValue(data);
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<label class="text-base font-bold" for={generateId(item.point)}>
|
||||
{item.point}
|
||||
</label>
|
||||
</td>
|
||||
<td>
|
||||
<div class={`badge gap-2 ${getBadgeClass(item.priority, 'badge-')}`}>
|
||||
{item.priority}
|
||||
</div>
|
||||
</td>
|
||||
<td dangerouslySetInnerHTML={parseMarkdown(item.details)}></td>
|
||||
</tr>
|
||||
))}
|
||||
{props.section.checklist.map((item, index) => {
|
||||
const badgeColor = getBadgeClass(item.priority);
|
||||
const itemId = generateId(item.point);
|
||||
const isItemCompleted = isChecked(itemId);
|
||||
const isItemIgnored = isIgnored(itemId);
|
||||
return (
|
||||
<tr key={index} class={[
|
||||
'rounded-sm transition-all',
|
||||
isItemCompleted ? `bg-${badgeColor} bg-opacity-10` : '',
|
||||
isItemIgnored? 'bg-neutral bg-opacity-15' : '',
|
||||
!isItemIgnored && !isItemCompleted ? `hover:bg-opacity-5 hover:bg-${badgeColor}` : '',
|
||||
]}>
|
||||
<td class="text-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
class={`checkbox checked:checkbox-${badgeColor} hover:checkbox-${badgeColor}`}
|
||||
id={`done-${itemId}`}
|
||||
checked={isChecked(itemId)}
|
||||
disabled={isIgnored(itemId)}
|
||||
onClick$={() => {
|
||||
const data = completed.value;
|
||||
data[itemId] = !data[itemId];
|
||||
setCompleted(data);
|
||||
}}
|
||||
/>
|
||||
<label for={`ignore-${itemId}`} class="text-small block opacity-50 mt-2">Ignore</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`ignore-${itemId}`}
|
||||
class={`toggle toggle-xs toggle-${badgeColor}`}
|
||||
checked={isIgnored(itemId)}
|
||||
onClick$={() => {
|
||||
const ignoredData = ignored.value;
|
||||
ignoredData[itemId] = !ignoredData[itemId];
|
||||
setIgnored(ignoredData);
|
||||
|
||||
const completedData = completed.value;
|
||||
completedData[itemId] = false;
|
||||
setCompleted(completedData);
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<label
|
||||
for={`done-${itemId}`}
|
||||
class={`text-base font-bold ${isIgnored(itemId) ? 'line-through' : 'cursor-pointer'}`}>
|
||||
{item.point}
|
||||
</label>
|
||||
</td>
|
||||
<td>
|
||||
<div class={`badge gap-2 badge-${badgeColor}`}>
|
||||
{item.priority}
|
||||
</div>
|
||||
</td>
|
||||
<td dangerouslySetInnerHTML={parseMarkdown(item.details)}></td>
|
||||
</tr>
|
||||
)}
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { $, component$, useSignal, useOnWindow, useContext } from "@builder.io/qwik";
|
||||
import { Chart, registerables } from 'chart.js';
|
||||
|
||||
import { useLocalStorage } from "~/hooks/useLocalStorage";
|
||||
import { ChecklistContext } from "~/store/checklist-context";
|
||||
@ -18,6 +19,8 @@ export default component$(() => {
|
||||
const [checkedItems] = useLocalStorage('PSC_PROGRESS', {});
|
||||
// Store to hold calculated progress results
|
||||
const totalProgress = useSignal({ completed: 0, outOf: 0 });
|
||||
// Ref to the radar chart canvas
|
||||
const radarChart = useSignal<HTMLCanvasElement>();
|
||||
|
||||
/**
|
||||
* Calculates the users progress over specified sections.
|
||||
@ -142,6 +145,111 @@ export default component$(() => {
|
||||
makeDataAndDrawChart('advanced', 'hsl(var(--er, 0 91% 71%))');
|
||||
}));
|
||||
|
||||
|
||||
interface RadarChartData {
|
||||
labels: string[];
|
||||
datasets: {
|
||||
label: string;
|
||||
data: number[];
|
||||
[key: string]: any; // Anything else goes!
|
||||
}[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the multi-dimensional data used for the radar chart
|
||||
* based on each section, each level of priority, and the progress
|
||||
* @param sections - The sections to build data from
|
||||
*/
|
||||
const makeRadarData = $((sections: Sections): Promise<RadarChartData> => {
|
||||
// The labels for the corners of the chart, based on sections
|
||||
const labels = sections.map((section: Section) => section.title);
|
||||
// Items applied to every dataset
|
||||
const datasetTemplate = {
|
||||
borderWidth: 1,
|
||||
};
|
||||
// Helper function to asynchronously calculate percentage
|
||||
const calculatePercentage = async (section: Section, priority: Priority) => {
|
||||
const filteredSections = await filterByPriority([section], priority);
|
||||
const progress = await calculateProgress(filteredSections);
|
||||
return progress.outOf > 0 ? (progress.completed / progress.outOf) * 100 : 0;
|
||||
};
|
||||
|
||||
// Asynchronously build data for each priority level
|
||||
const buildDataForPriority = (priority: Priority, color: string) => {
|
||||
return Promise.all(sections.map(section => calculatePercentage(section, priority)))
|
||||
.then(data => ({
|
||||
...datasetTemplate,
|
||||
label: priority.charAt(0).toUpperCase() + priority.slice(1),
|
||||
data: data,
|
||||
backgroundColor: color,
|
||||
}));
|
||||
};
|
||||
|
||||
// Wait on each set to resolve, and return the final data object
|
||||
return Promise.all([
|
||||
buildDataForPriority('recommended', 'hsl(158 64% 52%/75%)'),
|
||||
buildDataForPriority('optional', 'hsl(43 96% 56%/75%)'),
|
||||
buildDataForPriority('advanced', 'hsl(0 91% 71%/75%)'),
|
||||
]).then(datasets => ({
|
||||
labels,
|
||||
datasets,
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
|
||||
useOnWindow('load', $(() => {
|
||||
Chart.register(...registerables);
|
||||
|
||||
makeRadarData(checklists.value).then((data) => {
|
||||
if (radarChart.value) {
|
||||
new Chart(radarChart.value, {
|
||||
type: 'radar',
|
||||
data,
|
||||
options: {
|
||||
responsive: true,
|
||||
scales: {
|
||||
r: {
|
||||
angleLines: {
|
||||
display: true,
|
||||
color: '#7d7d7da1',
|
||||
},
|
||||
suggestedMin: 0,
|
||||
suggestedMax: 100,
|
||||
ticks: {
|
||||
stepSize: 25,
|
||||
callback: (value) => `${value}%`,
|
||||
color: '#ffffffbf',
|
||||
backdropColor: '#ffffff3b',
|
||||
},
|
||||
grid: {
|
||||
display: true,
|
||||
color: '#7d7d7dd4',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
labels: {
|
||||
font: {
|
||||
size: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: (ctx) => `Completed ${ctx.parsed.r}% of ${ctx.dataset.label || ''} items`,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
const items = [
|
||||
{ id: 'recommended-container', label: 'Essential' },
|
||||
{ id: 'optional-container', label: 'Optional' },
|
||||
@ -150,9 +258,11 @@ export default component$(() => {
|
||||
|
||||
// Beware, some god-awful markup ahead (thank Tailwind for that!)
|
||||
return (
|
||||
<div class="flex justify-center flex-col w-full items-center">
|
||||
<div class="mb-4">
|
||||
<div class="rounded-box bg-neutral-content bg-opacity-5 w-96 p-4">
|
||||
<div class="flex justify-center flex-wrap items-stretch gap-6 mb-4">
|
||||
|
||||
<div class="flex justify-center flex-col items-center gap-6">
|
||||
{/* Progress Percent */}
|
||||
<div class="rounded-box bg-front shadow-md w-96 p-4">
|
||||
<h3 class="text-primary text-2xl">Your Progress</h3>
|
||||
<p class="text-lg">
|
||||
You've completed <b>{totalProgress.value.completed} out of {totalProgress.value.outOf}</b> items
|
||||
@ -163,17 +273,35 @@ export default component$(() => {
|
||||
max={totalProgress.value.outOf}>
|
||||
</progress>
|
||||
</div>
|
||||
</div>
|
||||
<div class="carousel rounded-box mb-8">
|
||||
{items.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
class="flex flex-col justify-items-center carousel-item w-20 p-4
|
||||
bg-neutral-content bg-opacity-5 mx-2.5 rounded-box">
|
||||
<div class="relative" id={item.id}></div>
|
||||
<p class="text-center">{item.label}</p>
|
||||
|
||||
{/* Completion per level */}
|
||||
<div class="carousel rounded-box">
|
||||
{items.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
class="flex flex-col justify-items-center carousel-item w-20 p-4
|
||||
bg-front shadow-md mx-2.5 rounded-box">
|
||||
<div class="relative" id={item.id}></div>
|
||||
<p class="text-center">{item.label}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* Something ??? */}
|
||||
<div class="p-4 rounded-box bg-front shadow-md w-96 flex-grow">
|
||||
<p>Something else will go here....</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Radar Chart showing total progress per category and level */}
|
||||
<div class="rounded-box bg-front shadow-md w-96 p-4">
|
||||
<canvas ref={radarChart} id="myChart"></canvas>
|
||||
</div>
|
||||
|
||||
<div class="justify-center flex-col items-center gap-6 hidden xl:flex">
|
||||
{/* Remaining Tasks */}
|
||||
<div class="p-4 rounded-box bg-front shadow-md w-96 flex-grow">
|
||||
<p>Something else will go here....</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,6 +1,5 @@
|
||||
|
||||
.container {
|
||||
display: grid;
|
||||
gap: 2rem;
|
||||
/* I couldn't figure out how to do this with Tailwind.... */
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||
}
|
||||
|
@ -7,13 +7,15 @@ import styles from './psc.module.css';
|
||||
|
||||
export default component$((props: { sections: Section[] }) => {
|
||||
return (
|
||||
<div class={[styles.container, 'transition-all', 'px-4', 'mx-auto', 'max-w-6xl', 'w-full']}>
|
||||
<div class={[styles.container, 'grid',
|
||||
'mx-auto mt-8 px-4 gap-7', 'xl:px-10 xl:max-w-7xl',
|
||||
'transition-all', 'max-w-6xl w-full']}>
|
||||
{props.sections.map((section: Section) => (
|
||||
<a key={section.slug}
|
||||
href={`/checklist/${section.slug}`}
|
||||
class={['card card-side bg-base-200 bg-opacity-25 shadow-xl transition-all px-2',
|
||||
`outline outline-10 outline-offset-2 outline-${section.color}-400`,
|
||||
`hover:outline-offset-4 hover:bg-opacity-15 hover:bg-${section.color}-600`]}
|
||||
class={['card card-side bg-front bg-opacity-25 shadow-md transition-all px-2',
|
||||
`outline-offset-2 outline-${section.color}-400`,
|
||||
`hover:outline hover:outline-10 hover:outline-offset-4 hover:bg-opacity-15 hover:bg-${section.color}-600`]}
|
||||
>
|
||||
<div class="flex-shrink-0 flex flex-col py-4 h-auto items-stretch justify-evenly">
|
||||
<Icon icon={section.icon || 'star'} color={section.color} />
|
||||
|
@ -5,11 +5,11 @@ import type { DocumentHead } from "@builder.io/qwik-city";
|
||||
export default component$(() => {
|
||||
return (
|
||||
<div class="m-4 md:mx-16">
|
||||
<article class="bg-base-200 bg-opacity-25 p-8 mx-auto max-w-[1200px] m-8 rounded-lg shadow-lg">
|
||||
<article class="bg-back p-8 mx-auto max-w-[1200px] m-8 rounded-lg shadow-md">
|
||||
<h2 class="text-2xl">About the Security Checklist</h2>
|
||||
</article>
|
||||
<div class="divider"></div>
|
||||
<article class="bg-base-200 bg-opacity-25 p-8 mx-auto max-w-[1200px] m-8 rounded-lg shadow-lg">
|
||||
<article class="bg-back p-8 mx-auto max-w-[1200px] m-8 rounded-lg shadow-md">
|
||||
<h2 class="text-2xl">About the Author</h2>
|
||||
<p>
|
||||
This project was originally started by me, Alicia Sykes- with a lot of help from the community.
|
||||
|
@ -22,7 +22,7 @@ export default component$(() => {
|
||||
|
||||
return (
|
||||
<div class="md:my-8 md:px-16 sm:px-2 rounded-md">
|
||||
<article class="bg-base-200 bg-opacity-25 p-8 mx-auto w-full max-w-[1200px] rounded-lg shadow-lg">
|
||||
<article class="bg-back p-8 mx-auto w-full max-w-[1200px] rounded-lg shadow-md">
|
||||
<h1 class={['gap-2 text-5xl font-bold capitalize flex']}>
|
||||
<Icon height={36} width={36} icon={section?.icon || 'star'} />
|
||||
{section?.title}
|
||||
|
@ -1,41 +1,41 @@
|
||||
|
||||
|
||||
const applyCustomColors = (theme, front, back) => {
|
||||
return {
|
||||
...require("daisyui/src/theming/themes")[`[data-theme=${theme}]`],
|
||||
"--front":front,
|
||||
"--back": back || `${front} /0.75`,
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
|
||||
plugins: [require('daisyui')],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
"front": "hsl(var(--front, 0deg 0% 60% / 10%))",
|
||||
"back": "hsl(var(--back, 212 14% 10% / 1))",
|
||||
},
|
||||
},
|
||||
},
|
||||
daisyui: {
|
||||
themes: [
|
||||
"light",
|
||||
"dark",
|
||||
"cupcake",
|
||||
"bumblebee",
|
||||
"emerald",
|
||||
"corporate",
|
||||
"synthwave",
|
||||
"retro",
|
||||
"cyberpunk",
|
||||
"valentine",
|
||||
"halloween",
|
||||
"garden",
|
||||
"forest",
|
||||
"aqua",
|
||||
"lofi",
|
||||
"pastel",
|
||||
"fantasy",
|
||||
"wireframe",
|
||||
"black",
|
||||
"luxury",
|
||||
"dracula",
|
||||
"cmyk",
|
||||
"autumn",
|
||||
"business",
|
||||
"acid",
|
||||
"lemonade",
|
||||
"night",
|
||||
"coffee",
|
||||
"winter",
|
||||
"dim",
|
||||
"nord",
|
||||
"sunset",
|
||||
{ light: applyCustomColors("light", "237 9% 86% / 0.75", "237 9% 86% / 1") },
|
||||
{ dark: applyCustomColors("dark", "217 14% 17%", "212 14% 10%") },
|
||||
{ night: applyCustomColors("night", "220deg 44.68% 9.22%", "219.2, 38.2%, 13.3%") },
|
||||
|
||||
{ cupcake: applyCustomColors("cupcake", "297deg 77% 90%", "303.33deg 60% 94.12%") },
|
||||
{ bumblebee: applyCustomColors("bumblebee", "75.5deg 40% 87%", "60deg 23.08% 92.35%") },
|
||||
{ corporate: applyCustomColors("corporate", "211.67deg 43.9% 83.92%", "212.3, 25.5%, 90%") },
|
||||
{ synthwave: applyCustomColors("synthwave", "253.3, 58.1%, 12.2%", "253.5, 47.6%, 16.5%") },
|
||||
{ retro: applyCustomColors("retro", "41.9, 37.1%, 72%", "42.5, 36.4%, 87.1%") },
|
||||
{ valentine: applyCustomColors("valentine", "320.4, 70.7%, 85.3%", "322.1, 61.3%, 93.9%") },
|
||||
{ halloween: applyCustomColors("halloween", "0, 0%, 9%", "0, 0%, 16.9%") },
|
||||
{ aqua: applyCustomColors("aqua", "230.5, 41%, 27.3%", "230.8, 33.9%, 22.5%") },
|
||||
{ lofi: applyCustomColors("lofi", "228, 11.6%, 91.6%") },
|
||||
{ fantasy: applyCustomColors("fantasy", "230.8, 33.9%, 22.5%", "210, 2.3%, 83.1%") },
|
||||
{ dracula: applyCustomColors("dracula", "210, 2.3%, 83.1%, 0.03", "228, 20%, 14.7%") },
|
||||
],
|
||||
},
|
||||
safelist: [
|
||||
@ -44,21 +44,8 @@ module.exports = {
|
||||
variants: ['light', 'dark', 'hover', 'focus'],
|
||||
},
|
||||
{
|
||||
pattern: /(badge|bg)-(success|warning|error|info|neutral)/,
|
||||
variants: ['light', 'dark', 'hover', 'focus'],
|
||||
pattern: /(badge|bg|checkbox|toggle)-(success|warning|error|info|neutral)/,
|
||||
variants: ['light', 'dark', 'hover', 'focus', 'checked'],
|
||||
}
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
'custom-blue': '#5b6d5b',
|
||||
'custom-pink': {
|
||||
100: '#ffecef',
|
||||
400: '#ff69b4',
|
||||
800: '#ff1493',
|
||||
},
|
||||
// Add more custom colors or extend existing ones here
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -480,6 +480,11 @@
|
||||
"@jridgewell/resolve-uri" "^3.1.0"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||
|
||||
"@kurkle/color@^0.3.0":
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.2.tgz#5acd38242e8bde4f9986e7913c8fdf49d3aa199f"
|
||||
integrity sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==
|
||||
|
||||
"@mdx-js/mdx@2.3.0":
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-2.3.0.tgz#d65d8c3c28f3f46bb0e7cb3bf7613b39980671a9"
|
||||
@ -1019,6 +1024,13 @@ character-reference-invalid@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9"
|
||||
integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==
|
||||
|
||||
chart.js@^4.4.1:
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.4.1.tgz#ac5dc0e69a7758909158a96fe80ce43b3bb96a9f"
|
||||
integrity sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==
|
||||
dependencies:
|
||||
"@kurkle/color" "^0.3.0"
|
||||
|
||||
chokidar@^3.5.3:
|
||||
version "3.5.3"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
|
||||
|
Loading…
Reference in New Issue
Block a user