From 4cf0bca49edd0f1799a0118419fd3fb5406068f4 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Tue, 30 Dec 2025 16:34:59 +0200 Subject: [PATCH] Refactor elements.css to CSS Modules --- app/components/TimePopover.tsx | 5 +- app/components/elements/Calendar.module.css | 81 +++++++ app/components/elements/Calendar.tsx | 39 +++- app/components/elements/DatePicker.module.css | 46 ++++ app/components/elements/DatePicker.tsx | 19 +- app/components/elements/Label.module.css | 6 + app/components/elements/Label.tsx | 5 +- app/components/elements/Popover.module.css | 14 ++ app/components/elements/Popover.tsx | 5 +- app/components/elements/Switch.module.css | 61 +++++ app/components/elements/Switch.tsx | 5 +- app/root.tsx | 1 - app/styles/elements.css | 212 ------------------ 13 files changed, 262 insertions(+), 237 deletions(-) create mode 100644 app/components/elements/Calendar.module.css create mode 100644 app/components/elements/DatePicker.module.css create mode 100644 app/components/elements/Label.module.css create mode 100644 app/components/elements/Popover.module.css create mode 100644 app/components/elements/Switch.module.css delete mode 100644 app/styles/elements.css diff --git a/app/components/TimePopover.tsx b/app/components/TimePopover.tsx index 933efa29c..8a18515b0 100644 --- a/app/components/TimePopover.tsx +++ b/app/components/TimePopover.tsx @@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next"; import { useCopyToClipboard } from "react-use"; import { useTimeFormat } from "~/hooks/useTimeFormat"; import { SendouButton } from "./elements/Button"; +import popoverStyles from "./elements/Popover.module.css"; import { CheckmarkIcon } from "./icons/Checkmark"; import { ClipboardIcon } from "./icons/Clipboard"; @@ -65,11 +66,11 @@ export default function TimePopover({ - +
{formatTime(time, { diff --git a/app/components/elements/Calendar.module.css b/app/components/elements/Calendar.module.css new file mode 100644 index 000000000..bebf2455e --- /dev/null +++ b/app/components/elements/Calendar.module.css @@ -0,0 +1,81 @@ +.root { + background-color: var(--color-bg); + border-radius: var(--rounded); + padding: var(--s-4); + border: var(--border-style); + max-width: fit-content; +} + +.header { + display: flex; + gap: var(--s-2); + justify-content: space-between; + align-items: center; + margin-block-end: var(--s-1); + min-width: 250px; +} + +.heading { + font-size: var(--fonts-lg); +} + +.navButton { + background-color: transparent; + color: var(--color-text-accent); + border: none; + padding: 0; + border-radius: 100%; + + &:focus-visible { + outline: var(--input-focus-ring); + outline-offset: 1px; + } +} + +.navIcon { + fill: var(--color-text-accent); + width: 27.5px; +} + +.grid { + width: 100%; +} + +.headerCell { + color: var(--color-text-high); +} + +.cell { + display: grid; + place-items: center; + font-size: var(--fonts-sm); + padding: var(--s-1-5); + width: 35px; + height: 35px; + border-radius: var(--rounded-sm); + outline-color: var(--color-accent); + + &:focus-visible { + outline: var(--input-focus-ring); + outline-offset: 1px; + } +} + +.cell[data-outside-month] { + display: none; +} + +.cell[data-disabled] { + opacity: 0.5; + background-color: transparent !important; +} + +.cell[data-selected] { + background-color: var(--color-bg-high); + color: var(--color-text-accent); +} + +.cell:hover { + background-color: var(--color-bg-high); + outline: initial; +} diff --git a/app/components/elements/Calendar.tsx b/app/components/elements/Calendar.tsx index 816882e62..f62fd204f 100644 --- a/app/components/elements/Calendar.tsx +++ b/app/components/elements/Calendar.tsx @@ -4,12 +4,16 @@ import { Calendar, CalendarCell, CalendarGrid, + CalendarGridBody, + CalendarGridHeader, + CalendarHeaderCell, type CalendarProps, type DateValue, Heading, } from "react-aria-components"; import { ArrowLeftIcon } from "~/components/icons/ArrowLeft"; import { ArrowRightIcon } from "~/components/icons/ArrowRight"; +import styles from "./Calendar.module.css"; export interface SendouCalendarProps extends CalendarProps { @@ -21,20 +25,33 @@ export function SendouCalendar({ ...rest }: SendouCalendarProps) { return ( - -
- - -
- - {(date) => { - return ; - }} + + + {(day) => ( + + {day} + + )} + + + {(date) => ( + + )} +
); diff --git a/app/components/elements/DatePicker.module.css b/app/components/elements/DatePicker.module.css new file mode 100644 index 000000000..46295c1e5 --- /dev/null +++ b/app/components/elements/DatePicker.module.css @@ -0,0 +1,46 @@ +.group { + height: var(--input-height); + padding: 0 var(--input-padding-inline); + border: 2px solid var(--color-border); + border-radius: var(--rounded-sm); + background-color: var(--color-bg); + outline: none; + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--s-1-5); + width: 100%; + cursor: pointer; +} + +.root[data-open] .group { + border-color: transparent; + outline: 2px solid var(--color-text-accent); +} + +.dateInput { + display: flex; +} + +.segment:not([data-type="literal"]) { + padding: 0 var(--s-1); + border-radius: 5px; +} + +.segment:focus-visible { + background-color: var(--color-bg-high); + color: var(--color-text-accent); + outline: none; +} + +.button { + margin-inline-start: auto; + background-color: transparent; + border: none; + outline: none; +} + +.icon { + fill: var(--color-text); + width: 17.5px; +} diff --git a/app/components/elements/DatePicker.tsx b/app/components/elements/DatePicker.tsx index d5fe553c0..40f26ffad 100644 --- a/app/components/elements/DatePicker.tsx +++ b/app/components/elements/DatePicker.tsx @@ -12,6 +12,7 @@ import { import { SendouBottomTexts } from "~/components/elements/BottomTexts"; import { SendouCalendar } from "~/components/elements/Calendar"; import { CalendarIcon } from "../icons/Calendar"; +import styles from "./DatePicker.module.css"; import { SendouLabel } from "./Label"; interface SendouDatePickerProps @@ -29,12 +30,20 @@ export function SendouDatePicker({ ...rest }: SendouDatePickerProps) { return ( - + {label} - - {(segment) => } - diff --git a/app/components/elements/Label.module.css b/app/components/elements/Label.module.css new file mode 100644 index 000000000..22afced17 --- /dev/null +++ b/app/components/elements/Label.module.css @@ -0,0 +1,6 @@ +.label { + font-size: var(--fonts-xs); + font-weight: var(--bold); + margin-block-end: var(--label-margin); + display: block; +} diff --git a/app/components/elements/Label.tsx b/app/components/elements/Label.tsx index 37622f1c5..8cc01940b 100644 --- a/app/components/elements/Label.tsx +++ b/app/components/elements/Label.tsx @@ -1,4 +1,5 @@ import { Label as ReactAriaLabel } from "react-aria-components"; +import styles from "./Label.module.css"; export function SendouLabel({ children, @@ -8,8 +9,8 @@ export function SendouLabel({ required?: boolean; }) { return ( - - {children} {required && *} + + {children} {required ? * : null} ); } diff --git a/app/components/elements/Popover.module.css b/app/components/elements/Popover.module.css new file mode 100644 index 000000000..0e66cf1c5 --- /dev/null +++ b/app/components/elements/Popover.module.css @@ -0,0 +1,14 @@ +.content { + max-width: 20rem; + padding: var(--s-2); + border: 2px solid var(--color-border); + border-radius: var(--rounded); + background-color: var(--color-bg); + font-size: var(--fonts-sm); + font-weight: var(--semi-bold); + white-space: pre-wrap; +} + +.dialog { + outline: none; +} diff --git a/app/components/elements/Popover.tsx b/app/components/elements/Popover.tsx index ce4697d4c..3d22ec94d 100644 --- a/app/components/elements/Popover.tsx +++ b/app/components/elements/Popover.tsx @@ -5,6 +5,7 @@ import { Popover, type PopoverProps, } from "react-aria-components"; +import styles from "./Popover.module.css"; /** * A reusable popover component that wraps around a trigger element (SendouButton or Button from React Aria Components library). @@ -38,11 +39,11 @@ export function SendouPopover({ {trigger} - {children} + {children} ); diff --git a/app/components/elements/Switch.module.css b/app/components/elements/Switch.module.css new file mode 100644 index 000000000..1356f677f --- /dev/null +++ b/app/components/elements/Switch.module.css @@ -0,0 +1,61 @@ +.root { + --height: var(--toggle-height); + + cursor: pointer; + display: grid; + grid-template-columns: auto 1fr; + align-items: center; + gap: 0.571rem; + color: var(--text-color); + forced-color-adjust: none; + font-size: var(--fonts-xs); + font-weight: var(--bold); + margin-block-end: 0; +} + +.small { + --height: var(--toggle-height-small); +} + +.indicator { + width: calc(var(--height) * 1.75); + height: var(--height); + background: var(--color-bg); + border: 2px solid var(--color-border); + border-radius: 99999px; + + &:before { + content: ""; + display: block; + margin: 2px; + width: calc(var(--height) - 8px); + height: calc(var(--height) - 8px); + aspect-ratio: 1 / 1; + background: var(--color-border); + border-radius: 50%; + transition: transform 200ms; + } +} + +.root[data-selected] .indicator { + background: var(--color-text-accent); + border-color: var(--color-text-accent); + + &:before { + transform: translateX(calc(var(--height) * 0.75)); + background: var(--color-text-inverse); + } +} + +.root[data-focus-visible] .indicator { + outline: 2px solid var(--color-accent-high); + outline-offset: 1px; +} + +.root[data-disabled] .indicator { + opacity: 0.65; +} + +.root[data-disabled] { + cursor: not-allowed; +} diff --git a/app/components/elements/Switch.tsx b/app/components/elements/Switch.tsx index 6572a9414..7f5d2c883 100644 --- a/app/components/elements/Switch.tsx +++ b/app/components/elements/Switch.tsx @@ -3,6 +3,7 @@ import { Switch as ReactAriaSwitch, type SwitchProps as ReactAriaSwitchProps, } from "react-aria-components"; +import styles from "./Switch.module.css"; interface SendouSwitchProps extends ReactAriaSwitchProps { children?: React.ReactNode; @@ -13,9 +14,9 @@ export function SendouSwitch({ children, size, ...rest }: SendouSwitchProps) { return ( -
+
{children} ); diff --git a/app/root.tsx b/app/root.tsx index 572ca66f8..0e3b91146 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -56,7 +56,6 @@ import { SUSPENDED_PAGE } from "./utils/urls"; import "~/styles/vars.css"; import "~/styles/normalize.css"; import "~/styles/common.css"; -import "~/styles/elements.css"; import "~/styles/utils.css"; import "~/styles/flags.css"; import "nprogress/nprogress.css"; diff --git a/app/styles/elements.css b/app/styles/elements.css deleted file mode 100644 index eec072352..000000000 --- a/app/styles/elements.css +++ /dev/null @@ -1,212 +0,0 @@ -.sendou-popover-content { - max-width: 20rem; - padding: var(--s-2); - border: 2px solid var(--color-border); - border-radius: var(--rounded); - background-color: var(--color-bg); - font-size: var(--fonts-sm); - font-weight: var(--semi-bold); - white-space: pre-wrap; -} - -.react-aria-Dialog { - outline: none; -} - -.react-aria-Label { - font-size: var(--fonts-xs); - font-weight: var(--bold); - margin-block-end: var(--label-margin); - display: block; -} - -.react-aria-DatePicker .react-aria-Group { - height: var(--input-height); - padding: 0 var(--input-padding-inline); - border: 2px solid var(--color-border); - border-radius: var(--rounded-sm); - background-color: var(--color-bg); - outline: none; - display: flex; - align-items: center; - justify-content: space-between; - gap: var(--s-1-5); - width: 100%; - cursor: pointer; -} - -.react-aria-DatePicker[data-open] .react-aria-Group { - border-color: transparent; - outline: 2px solid var(--color-text-accent); -} - -.react-aria-DatePicker .react-aria-DateInput { - display: flex; -} - -.react-aria-DateSegment:not([data-type="literal"]) { - padding: 0 var(--s-1); - border-radius: 5px; -} - -.react-aria-DateSegment:focus-visible { - background-color: var(--color-bg-high); - color: var(--color-text-accent); - outline: none; -} - -.react-aria-DatePicker .react-aria-Button { - margin-inline-start: auto; - background-color: transparent; - border: none; - outline: none; -} - -.react-aria-DatePicker svg { - fill: var(--color-text); - width: 17.5px; -} - -.react-aria-Calendar { - background-color: var(--color-bg); - border-radius: var(--rounded); - padding: var(--s-4); - border: var(--border-style); - max-width: fit-content; -} - -.react-aria-Calendar header { - display: flex; - gap: var(--s-2); - justify-content: space-between; - align-items: center; - margin-block-end: var(--s-1); - min-width: 250px; -} - -.react-aria-Calendar .react-aria-Heading { - font-size: var(--fonts-lg); -} - -.react-aria-Calendar .react-aria-Button { - background-color: transparent; - color: var(--color-text-accent); - border: none; - padding: 0; - border-radius: 100%; - - &:focus-visible { - outline: var(--input-focus-ring); - outline-offset: 1px; - } -} - -.react-aria-Calendar .react-aria-Button svg { - fill: var(--color-text-accent); - width: 27.5px; -} - -.react-aria-CalendarGrid { - width: 100%; -} - -.react-aria-CalendarHeaderCell { - color: var(--color-text-high); -} - -.react-aria-CalendarCell { - display: grid; - place-items: center; - font-size: var(--fonts-sm); - padding: var(--s-1-5); - width: 35px; - height: 35px; - border-radius: var(--rounded-sm); - outline-color: var(--color-accent); - - &.react-aria-CalendarCell:focus-visible { - outline: var(--input-focus-ring); - outline-offset: 1px; - } -} - -.react-aria-CalendarCell[data-outside-month] { - display: none; -} - -.react-aria-CalendarCell[data-disabled] { - opacity: 0.5; - background-color: transparent !important; -} - -.react-aria-Calendar .react-aria-CalendarCell[data-selected] { - background-color: var(--color-bg-high); - color: var(--color-text-accent); -} - -.react-aria-CalendarCell:hover { - background-color: var(--color-bg-high); - outline: initial; -} - -.react-aria-Switch { - --height: var(--toggle-height); - - cursor: pointer; - display: grid; - grid-template-columns: auto 1fr; - align-items: center; - gap: 0.571rem; - color: var(--text-color); - forced-color-adjust: none; - font-size: var(--fonts-xs); - font-weight: var(--bold); - margin-block-end: 0; - - &.small { - --height: var(--toggle-height-small); - } -} - -.react-aria-Switch .indicator { - width: calc(var(--height) * 1.75); - height: var(--height); - background: var(--color-bg); - border: 2px solid var(--color-border); - border-radius: 99999px; - - &:before { - content: ""; - display: block; - margin: 2px; - width: calc(var(--height) - 8px); - height: calc(var(--height) - 8px); - aspect-ratio: 1 / 1; - background: var(--color-border); - border-radius: 50%; - transition: transform 200ms; - } -} - -.react-aria-Switch[data-selected] .indicator { - background: var(--color-text-accent); - border-color: var(--color-text-accent); - - &:before { - transform: translateX(calc(var(--height) * 0.75)); - background: var(--color-text-inverse); - } -} - -.react-aria-Switch[data-focus-visible] .indicator { - outline: 2px solid var(--color-accent-high); - outline-offset: 1px; -} - -.react-aria-Switch[data-disabled] .indicator { - opacity: 0.65; -} - -.react-aria-Switch[data-disabled] { - cursor: not-allowed; -}