mirror of
https://github.com/apricote/Listory.git
synced 2026-01-13 13:11:02 +00:00
feat(frontend): Dark Mode
This commit is contained in:
parent
65b0d24903
commit
78c7afc152
16 changed files with 83 additions and 62 deletions
|
|
@ -21,7 +21,7 @@ export function App() {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-screen justify-between">
|
||||
<div className="flex flex-col min-h-screen justify-between dark:bg-gray-900">
|
||||
<header>
|
||||
<NavBar />
|
||||
</header>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ const VERSION = process.env.REACT_APP_VERSION || "Unknown";
|
|||
|
||||
export const Footer: React.FC = () => {
|
||||
return (
|
||||
<div className="flex items-center justify-between flex-wrap bg-green-500 p-4 text-green-200 hover:text-white text-xs">
|
||||
<div className="flex items-center justify-between flex-wrap bg-green-500 dark:bg-gray-800 p-4 text-green-200 hover:text-white text-xs">
|
||||
<div>
|
||||
<a
|
||||
href={CHANGELOG_URL}
|
||||
|
|
|
|||
|
|
@ -9,13 +9,13 @@ export const LoginFailure: React.FC = () => {
|
|||
|
||||
return (
|
||||
<div
|
||||
className="bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4 m-8"
|
||||
className="bg-yellow-100 dark:bg-gray-800 border-l-4 border-yellow-500 text-yellow-700 dark:text-yellow-100 p-4 m-8"
|
||||
role="alert"
|
||||
>
|
||||
<p className="font-bold">Login Failure</p>
|
||||
<p>Something not ideal might be happening.</p>
|
||||
<p className="m-5 bg-gray-100 p-5">
|
||||
<ul className="text-xs text-gray-700 font-mono">
|
||||
<p className="m-5 bg-gray-100 dark:bg-gray-700 p-5">
|
||||
<ul className="text-xs text-gray-700 dark:text-gray-300 font-mono">
|
||||
<li>Source: {source}</li>
|
||||
<li>Reason: {reason}</li>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ import React from "react";
|
|||
import { Spinner } from "./Spinner";
|
||||
|
||||
export const LoginLoading: React.FC = () => (
|
||||
<main className="md:flex md:justify-center p-4">
|
||||
<div className="md:shrink-0 min-w-full md:min-w-0 md:w-1/3 l:w-1/5 xl:w-1/6 max-w-screen-lg shadow-xl bg-gray-100 rounded-lg m-2">
|
||||
<div className="text-white bg-green-500 rounded-lg rounded-b-none text-center mb-4 p-6">
|
||||
<main className="sm:flex sm:justify-center p-4 dark:bg-gray-900 h-screen">
|
||||
<div className="sm:shrink-0 min-w-full sm:min-w-0 sm:w-1/2 md:w-1/3 l:w-1/5 xl:w-1/5 2xl:w-1/6 max-w-screen-lg max-h-min h-min shadow-xl bg-gray-100 dark:bg-gray-800 rounded-lg m-2">
|
||||
<div className="text-white bg-green-500 dark:bg-gray-700 rounded-lg rounded-b-none text-center mb-4 p-6">
|
||||
<span className="font-semibold text-xl tracking-tight">Listory</span>
|
||||
</div>
|
||||
<p className="text-2xl font-extralight text-gray-700 text-center p-6">
|
||||
<p className="text-2xl font-extralight text-gray-700 dark:text-gray-300 text-center p-6">
|
||||
Logging in
|
||||
</p>
|
||||
<Spinner className="p-6" size={128} />
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export const NavBar: React.FC = () => {
|
|||
const { user, loginWithSpotifyProps } = useAuth();
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between flex-wrap bg-green-500 p-6">
|
||||
<div className="flex items-center justify-between flex-wrap bg-green-500 dark:bg-gray-800 p-6">
|
||||
<div className="flex items-center shrink-0 text-white mr-6">
|
||||
<span className="font-semibold text-xl tracking-tight">Listory</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -32,7 +32,9 @@ export const RecentListens: React.FC = () => {
|
|||
<div className="md:flex md:justify-center p-4">
|
||||
<div className="md:shrink-0 min-w-full xl:min-w-0 xl:w-2/3 max-w-screen-lg">
|
||||
<div className="flex justify-between">
|
||||
<p className="text-2xl font-normal text-gray-700">Recent listens</p>
|
||||
<p className="text-2xl font-normal text-gray-700 dark:text-gray-400">
|
||||
Recent listens
|
||||
</p>
|
||||
<button
|
||||
className="shrink-0 mx-2 bg-transparent hover:bg-green-500 text-green-500 hover:text-white font-semibold py-2 px-4 border border-green-500 hover:border-transparent rounded"
|
||||
onClick={reload}
|
||||
|
|
@ -40,16 +42,18 @@ export const RecentListens: React.FC = () => {
|
|||
<ReloadIcon className="w-5 h-5 fill-current" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="shadow-xl bg-gray-100 rounded p-2 m-2">
|
||||
<div className="shadow-xl bg-gray-100 dark:bg-gray-800 rounded p-2 m-2">
|
||||
{isLoading && <Spinner className="m-8" />}
|
||||
{recentListens.length === 0 && (
|
||||
<div>
|
||||
<p>Could not find any listens!</p>
|
||||
<div className="text-center m-4">
|
||||
<p className="text-gray-700 dark:text-gray-400">
|
||||
Could not find any listens!
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
{recentListens.length > 0 && (
|
||||
<div className="table-auto w-full text-gray-700">
|
||||
<div className="table-auto w-full">
|
||||
{recentListens.map((listen) => (
|
||||
<ListenItem listen={listen} key={listen.id} />
|
||||
))}
|
||||
|
|
@ -69,6 +73,9 @@ const Pagination: React.FC<{
|
|||
setPage: (newPage: number) => void;
|
||||
}> = ({ page, totalPages, setPage }) => {
|
||||
const disabledBtn = "opacity-50 cursor-default";
|
||||
const hoverBtn = "hover:bg-gray-400 dark:hover:bg-gray-600";
|
||||
const defaultBtn =
|
||||
"bg-gray-300 dark:bg-gray-700 text-gray-700 dark:text-gray-300 font-bold py-2 px-4";
|
||||
const pageButtons = getPaginationItems(page, totalPages, 1);
|
||||
|
||||
const isFirstPage = page === 1;
|
||||
|
|
@ -78,8 +85,8 @@ const Pagination: React.FC<{
|
|||
<div className="flex justify-center my-4">
|
||||
<button
|
||||
className={`${
|
||||
isFirstPage ? disabledBtn : "hover:bg-gray-400"
|
||||
} bg-gray-300 text-gray-700 font-bold py-2 px-4 rounded-l`}
|
||||
isFirstPage ? disabledBtn : hoverBtn
|
||||
} ${defaultBtn} rounded-l`}
|
||||
onClick={() => setPage(page - 1)}
|
||||
disabled={isFirstPage}
|
||||
>
|
||||
|
|
@ -88,11 +95,10 @@ const Pagination: React.FC<{
|
|||
{pageButtons.map((buttonPage, i) =>
|
||||
buttonPage ? (
|
||||
<button
|
||||
className={`${
|
||||
buttonPage === page
|
||||
? "bg-green-300 hover:bg-green-400"
|
||||
: "bg-gray-300 hover:bg-gray-400"
|
||||
} text-gray-700 font-bold py-2 px-4`}
|
||||
className={`${hoverBtn} ${defaultBtn} ${
|
||||
buttonPage === page &&
|
||||
"bg-green-400 dark:bg-green-500 hover:bg-green-400 dark:hover:bg-green-500 cursor-default"
|
||||
}`}
|
||||
onClick={() => setPage(buttonPage)}
|
||||
key={i}
|
||||
>
|
||||
|
|
@ -101,7 +107,7 @@ const Pagination: React.FC<{
|
|||
) : (
|
||||
<div
|
||||
key={i}
|
||||
className="cursor-default bg-gray-300/50 text-gray-700/50 font-bold py-2 px-4 rounded-l"
|
||||
className={`cursor-default ${disabledBtn} ${defaultBtn}`}
|
||||
>
|
||||
...
|
||||
</div>
|
||||
|
|
@ -109,8 +115,8 @@ const Pagination: React.FC<{
|
|||
)}
|
||||
<button
|
||||
className={`${
|
||||
isLastPage ? disabledBtn : "hover:bg-gray-400"
|
||||
} bg-gray-300 text-gray-700 font-bold py-2 px-4 rounded-r`}
|
||||
isLastPage ? disabledBtn : hoverBtn
|
||||
} ${defaultBtn} rounded-r`}
|
||||
onClick={() => setPage(page + 1)}
|
||||
disabled={isLastPage}
|
||||
>
|
||||
|
|
@ -128,7 +134,7 @@ const ListenItem: React.FC<{ listen: Listen }> = ({ listen }) => {
|
|||
});
|
||||
const dateTime = format(new Date(listen.playedAt), "PP p");
|
||||
return (
|
||||
<div className="hover:bg-gray-100 border-b border-gray-200 md:flex md:justify-around text-gray-700 px-2 py-2">
|
||||
<div className="hover:bg-gray-100 dark:hover:bg-gray-700 border-b border-gray-200 dark:border-gray-700/25 md:flex md:justify-around text-gray-700 dark:text-gray-300 px-2 py-2">
|
||||
<div className="md:w-1/2 font-bold">{trackName}</div>
|
||||
<div className=" md:w-1/3">{artists}</div>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -47,14 +47,16 @@ export const ReportListens: React.FC = () => {
|
|||
<div className="md:flex md:justify-center p-4">
|
||||
<div className="md:shrink-0 min-w-full xl:min-w-0 xl:w-2/3 max-w-screen-lg">
|
||||
<div className="flex justify-between">
|
||||
<p className="text-2xl font-normal text-gray-700">Listen Report</p>
|
||||
<p className="text-2xl font-normal text-gray-700 dark:text-gray-400">
|
||||
Listen Report
|
||||
</p>
|
||||
</div>
|
||||
<div className="shadow-xl bg-gray-100 rounded p-5 m-2">
|
||||
<div className="shadow-xl bg-gray-100 dark:bg-gray-800 rounded p-5 m-2">
|
||||
<div className="md:flex">
|
||||
<div className="text-gray-700">
|
||||
<div className="text-gray-700 dark:text-gray-300 mr-2">
|
||||
<label className="text-sm">Timeframe</label>
|
||||
<select
|
||||
className="block appearance-none min-w-full md:win-w-0 md:w-1/4 bg-white border border-gray-400 hover:border-gray-500 p-2 rounded shadow leading-tight focus:outline-none focus:ring"
|
||||
className="block appearance-none min-w-full md:win-w-0 md:w-1/4 bg-white dark:bg-gray-700 border border-gray-400 hover:border-gray-500 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:text-gray-200 p-2 rounded shadow leading-tight focus:outline-none focus:ring"
|
||||
onChange={(e) =>
|
||||
setTimeFrame(
|
||||
e.target.value as "day" | "week" | "month" | "year"
|
||||
|
|
@ -79,7 +81,7 @@ export const ReportListens: React.FC = () => {
|
|||
</div>
|
||||
)}
|
||||
{reportHasItems && (
|
||||
<div className="w-full text-gray-700 mt-5">
|
||||
<div className="w-full text-gray-700 dark:text-gray-300 mt-5">
|
||||
<ReportGraph timeFrame={timeFrame} data={report} />
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -111,7 +113,7 @@ const ReportGraph: React.FC<{
|
|||
const date = format(label as number, dateFormatFromTimeFrame(timeFrame));
|
||||
|
||||
return (
|
||||
<div className="bg-gray-100 shadow-xl p-2 rounded text-sm font-light">
|
||||
<div className="bg-gray-100 dark:bg-gray-700 shadow-xl p-2 rounded text-sm font-light">
|
||||
<p>{date}</p>
|
||||
<p>
|
||||
Listens: <span className="font-bold">{listens}</span>
|
||||
|
|
|
|||
|
|
@ -24,10 +24,10 @@ export const ReportTimeOptions: React.FC<ReportTimeOptionsProps> = ({
|
|||
}) => {
|
||||
return (
|
||||
<div className="md:flex mb-4">
|
||||
<div className="text-gray-700">
|
||||
<div className="text-gray-700 dark:text-gray-300">
|
||||
<label className="text-sm">Timeframe</label>
|
||||
<select
|
||||
className="block appearance-none min-w-full md:w-1/4 bg-white border border-gray-400 hover:border-gray-500 p-2 rounded shadow leading-tight focus:outline-none focus:ring"
|
||||
className="block appearance-none min-w-full md:w-1/4 bg-white dark:bg-gray-700 border border-gray-400 hover:border-gray-500 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:text-gray-200 p-2 rounded shadow leading-tight focus:outline-none focus:ring"
|
||||
onChange={(e) =>
|
||||
setTimeOptions({
|
||||
...timeOptions,
|
||||
|
|
@ -44,21 +44,25 @@ export const ReportTimeOptions: React.FC<ReportTimeOptionsProps> = ({
|
|||
</select>
|
||||
</div>
|
||||
{timeOptions.timePreset === TimePreset.CUSTOM && (
|
||||
<div className="md:flex text-gray-700">
|
||||
<DateSelect
|
||||
label="Start"
|
||||
value={timeOptions.customTimeStart}
|
||||
onChange={(newDate) =>
|
||||
setTimeOptions({ ...timeOptions, customTimeStart: newDate })
|
||||
}
|
||||
/>
|
||||
<DateSelect
|
||||
label="End"
|
||||
value={timeOptions.customTimeEnd}
|
||||
onChange={(newDate) =>
|
||||
setTimeOptions({ ...timeOptions, customTimeEnd: newDate })
|
||||
}
|
||||
/>
|
||||
<div className="md:flex text-gray-700 dark:text-gray-200">
|
||||
<div className="pl-2">
|
||||
<DateSelect
|
||||
label="Start"
|
||||
value={timeOptions.customTimeStart}
|
||||
onChange={(newDate) =>
|
||||
setTimeOptions({ ...timeOptions, customTimeStart: newDate })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="pl-2">
|
||||
<DateSelect
|
||||
label="End"
|
||||
value={timeOptions.customTimeEnd}
|
||||
onChange={(newDate) =>
|
||||
setTimeOptions({ ...timeOptions, customTimeEnd: newDate })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -36,9 +36,11 @@ export const ReportTopAlbums: React.FC = () => {
|
|||
<div className="md:flex md:justify-center p-4">
|
||||
<div className="md:shrink-0 min-w-full xl:min-w-0 xl:w-2/3 max-w-screen-lg">
|
||||
<div className="flex justify-between">
|
||||
<p className="text-2xl font-normal text-gray-700">Top Albums</p>
|
||||
<p className="text-2xl font-normal text-gray-700 dark:text-gray-400">
|
||||
Top Albums
|
||||
</p>
|
||||
</div>
|
||||
<div className="shadow-xl bg-gray-100 rounded p-5 m-2">
|
||||
<div className="shadow-xl bg-gray-100 dark:bg-gray-800 rounded p-5 m-2">
|
||||
<ReportTimeOptions
|
||||
timeOptions={timeOptions}
|
||||
setTimeOptions={setTimeOptions}
|
||||
|
|
|
|||
|
|
@ -35,9 +35,11 @@ export const ReportTopArtists: React.FC = () => {
|
|||
<div className="md:flex md:justify-center p-4">
|
||||
<div className="md:shrink-0 min-w-full xl:min-w-0 xl:w-2/3 max-w-screen-lg">
|
||||
<div className="flex justify-between">
|
||||
<p className="text-2xl font-normal text-gray-700">Top Artists</p>
|
||||
<p className="text-2xl font-normal text-gray-700 dark:text-gray-400">
|
||||
Top Artists
|
||||
</p>
|
||||
</div>
|
||||
<div className="shadow-xl bg-gray-100 rounded p-5 m-2">
|
||||
<div className="shadow-xl bg-gray-100 dark:bg-gray-800 rounded p-5 m-2">
|
||||
<ReportTimeOptions
|
||||
timeOptions={timeOptions}
|
||||
setTimeOptions={setTimeOptions}
|
||||
|
|
|
|||
|
|
@ -40,9 +40,11 @@ export const ReportTopGenres: React.FC = () => {
|
|||
<div className="md:flex md:justify-center p-4">
|
||||
<div className="md:shrink-0 min-w-full xl:min-w-0 xl:w-2/3 max-w-screen-lg">
|
||||
<div className="flex justify-between">
|
||||
<p className="text-2xl font-normal text-gray-700">Top Genres</p>
|
||||
<p className="text-2xl font-normal text-gray-700 dark:text-gray-400">
|
||||
Top Genres
|
||||
</p>
|
||||
</div>
|
||||
<div className="shadow-xl bg-gray-100 rounded p-5 m-2">
|
||||
<div className="shadow-xl bg-gray-100 dark:bg-gray-800 rounded p-5 m-2">
|
||||
<ReportTimeOptions
|
||||
timeOptions={timeOptions}
|
||||
setTimeOptions={setTimeOptions}
|
||||
|
|
|
|||
|
|
@ -37,9 +37,11 @@ export const ReportTopTracks: React.FC = () => {
|
|||
<div className="md:flex md:justify-center p-4">
|
||||
<div className="md:shrink-0 min-w-full xl:min-w-0 xl:w-2/3 max-w-screen-lg">
|
||||
<div className="flex justify-between">
|
||||
<p className="text-2xl font-normal text-gray-700">Top Tracks</p>
|
||||
<p className="text-2xl font-normal text-gray-700 dark:text-gray-400">
|
||||
Top Tracks
|
||||
</p>
|
||||
</div>
|
||||
<div className="shadow-xl bg-gray-100 rounded p-5 m-2">
|
||||
<div className="shadow-xl bg-gray-100 dark:bg-gray-800 rounded p-5 m-2">
|
||||
<ReportTimeOptions
|
||||
timeOptions={timeOptions}
|
||||
setTimeOptions={setTimeOptions}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export const Spinner: React.FC<SpinnerProps> = ({
|
|||
<div className={`flex justify-center ${className}`}>
|
||||
<svg
|
||||
role="status"
|
||||
className="text-gray-300 animate-spin fill-green-500"
|
||||
className="text-gray-300 dark:text-gray-700 animate-spin fill-green-500"
|
||||
height={size}
|
||||
width={size}
|
||||
viewBox="0 0 100 101"
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export const TopListItem: React.FC<TopListItemProps> = ({
|
|||
maxCount,
|
||||
}) => {
|
||||
return (
|
||||
<div className="group hover:bg-gray-200 bg-gray-100 border-b border-gray-200 md:justify-around text-gray-700 md:px-2">
|
||||
<div className="group bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700 border-b border-gray-200 dark:border-gray-700/25 md:justify-around text-gray-700 dark:text-gray-300 md:px-2">
|
||||
<div className="flex pt-2">
|
||||
<div className="md:flex w-11/12">
|
||||
<div className={`${subTitle ? "md:w-1/2" : "md:w-full"} font-bold`}>
|
||||
|
|
@ -29,10 +29,10 @@ export const TopListItem: React.FC<TopListItemProps> = ({
|
|||
<div className="w-1/12 self-center">{count}</div>
|
||||
</div>
|
||||
{maxCount && isMaxCountValid(maxCount) && (
|
||||
<div className="h-1 w-full bg-gradient-to-r from-teal-200 via-green-400 to-violet-400 flex flex-row-reverse">
|
||||
<div className="h-1 w-full bg-gradient-to-r from-teal-200/25 via-green-400 to-violet-400 dark:from-teal-700/25 dark:via-green-600/85 dark:to-amber-500 flex flex-row-reverse">
|
||||
<div
|
||||
style={{ width: numberToPercent(1 - count / maxCount) }}
|
||||
className="h-full group-hover:bg-gray-200 bg-gray-100"
|
||||
className="h-full group-hover:bg-gray-200 dark:group-hover:bg-gray-700 bg-gray-100 dark:bg-gray-800"
|
||||
></div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export const DateSelect: React.FC<DateSelectProps> = ({
|
|||
<div>
|
||||
<label className="text-sm">{label}</label>
|
||||
<input
|
||||
className="block appearance-none min-w-full md:win-w-0 md:w-1/4 bg-white border border-gray-400 hover:border-gray-500 p-2 rounded shadow leading-tight focus:outline-none focus:ring"
|
||||
className="block appearance-none min-w-full md:win-w-0 md:w-1/4 bg-white dark:bg-gray-700 border border-gray-400 hover:border-gray-500 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:text-gray-200 p-2 rounded shadow leading-tight focus:outline-none focus:ring"
|
||||
type="date"
|
||||
value={formatDateForDateInput(value)}
|
||||
onChange={(e) => {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ module.exports = {
|
|||
yellow: colors.yellow,
|
||||
teal: colors.teal,
|
||||
violet: colors.violet,
|
||||
amber: colors.amber,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue