mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-08-28 22:25:51 -05:00
Refactor elements.css to CSS Modules
This commit is contained in:
@@ -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({
|
||||
</button>
|
||||
<Popover
|
||||
isOpen={open}
|
||||
className={"sendou-popover-content"}
|
||||
className={popoverStyles.content}
|
||||
onOpenChange={setOpen}
|
||||
triggerRef={triggerRef}
|
||||
>
|
||||
<Dialog>
|
||||
<Dialog className={popoverStyles.dialog}>
|
||||
<div className="stack sm">
|
||||
<div className="text-center" suppressHydrationWarning>
|
||||
{formatTime(time, {
|
||||
|
||||
81
app/components/elements/Calendar.module.css
Normal file
81
app/components/elements/Calendar.module.css
Normal file
@@ -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;
|
||||
}
|
||||
@@ -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<T extends DateValue>
|
||||
extends CalendarProps<T> {
|
||||
@@ -21,20 +25,33 @@ export function SendouCalendar<T extends DateValue>({
|
||||
...rest
|
||||
}: SendouCalendarProps<T>) {
|
||||
return (
|
||||
<Calendar className={clsx(className, "react-aria-Calendar")} {...rest}>
|
||||
<header>
|
||||
<Button slot="previous">
|
||||
<ArrowLeftIcon />
|
||||
<Calendar className={clsx(className, styles.root)} {...rest}>
|
||||
<header className={styles.header}>
|
||||
<Button slot="previous" className={styles.navButton}>
|
||||
<ArrowLeftIcon className={styles.navIcon} />
|
||||
</Button>
|
||||
<Heading />
|
||||
<Button slot="next">
|
||||
<ArrowRightIcon />
|
||||
<Heading className={styles.heading} />
|
||||
<Button slot="next" className={styles.navButton}>
|
||||
<ArrowRightIcon className={styles.navIcon} />
|
||||
</Button>
|
||||
</header>
|
||||
<CalendarGrid>
|
||||
{(date) => {
|
||||
return <CalendarCell date={date} data-testid="choose-date-button" />;
|
||||
}}
|
||||
<CalendarGrid className={styles.grid}>
|
||||
<CalendarGridHeader>
|
||||
{(day) => (
|
||||
<CalendarHeaderCell className={styles.headerCell}>
|
||||
{day}
|
||||
</CalendarHeaderCell>
|
||||
)}
|
||||
</CalendarGridHeader>
|
||||
<CalendarGridBody>
|
||||
{(date) => (
|
||||
<CalendarCell
|
||||
date={date}
|
||||
className={styles.cell}
|
||||
data-testid="choose-date-button"
|
||||
/>
|
||||
)}
|
||||
</CalendarGridBody>
|
||||
</CalendarGrid>
|
||||
</Calendar>
|
||||
);
|
||||
|
||||
46
app/components/elements/DatePicker.module.css
Normal file
46
app/components/elements/DatePicker.module.css
Normal file
@@ -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;
|
||||
}
|
||||
@@ -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<T extends DateValue>
|
||||
@@ -29,12 +30,20 @@ export function SendouDatePicker<T extends DateValue>({
|
||||
...rest
|
||||
}: SendouDatePickerProps<T>) {
|
||||
return (
|
||||
<ReactAriaDatePicker {...rest} validationBehavior="aria">
|
||||
<ReactAriaDatePicker
|
||||
{...rest}
|
||||
validationBehavior="aria"
|
||||
className={styles.root}
|
||||
>
|
||||
<SendouLabel required={isRequired}>{label}</SendouLabel>
|
||||
<Group className="react-aria-Group">
|
||||
<DateInput>{(segment) => <DateSegment segment={segment} />}</DateInput>
|
||||
<Button data-testid="open-calendar-button">
|
||||
<CalendarIcon />
|
||||
<Group className={styles.group}>
|
||||
<DateInput className={styles.dateInput}>
|
||||
{(segment) => (
|
||||
<DateSegment segment={segment} className={styles.segment} />
|
||||
)}
|
||||
</DateInput>
|
||||
<Button data-testid="open-calendar-button" className={styles.button}>
|
||||
<CalendarIcon className={styles.icon} />
|
||||
</Button>
|
||||
</Group>
|
||||
<SendouBottomTexts bottomText={bottomText} errorText={errorText} />
|
||||
|
||||
6
app/components/elements/Label.module.css
Normal file
6
app/components/elements/Label.module.css
Normal file
@@ -0,0 +1,6 @@
|
||||
.label {
|
||||
font-size: var(--fonts-xs);
|
||||
font-weight: var(--bold);
|
||||
margin-block-end: var(--label-margin);
|
||||
display: block;
|
||||
}
|
||||
@@ -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 (
|
||||
<ReactAriaLabel>
|
||||
{children} {required && <span className="text-error">*</span>}
|
||||
<ReactAriaLabel className={styles.label}>
|
||||
{children} {required ? <span className="text-error">*</span> : null}
|
||||
</ReactAriaLabel>
|
||||
);
|
||||
}
|
||||
|
||||
14
app/components/elements/Popover.module.css
Normal file
14
app/components/elements/Popover.module.css
Normal file
@@ -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;
|
||||
}
|
||||
@@ -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({
|
||||
<DialogTrigger isOpen={isOpen}>
|
||||
{trigger}
|
||||
<Popover
|
||||
className={clsx("sendou-popover-content", popoverClassName)}
|
||||
className={clsx(styles.content, popoverClassName)}
|
||||
placement={placement}
|
||||
onOpenChange={onOpenChange}
|
||||
>
|
||||
<Dialog>{children}</Dialog>
|
||||
<Dialog className={styles.dialog}>{children}</Dialog>
|
||||
</Popover>
|
||||
</DialogTrigger>
|
||||
);
|
||||
|
||||
61
app/components/elements/Switch.module.css
Normal file
61
app/components/elements/Switch.module.css
Normal file
@@ -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;
|
||||
}
|
||||
@@ -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 (
|
||||
<ReactAriaSwitch
|
||||
{...rest}
|
||||
className={clsx("react-aria-Switch", { small: size === "small" })}
|
||||
className={clsx(styles.root, { [styles.small]: size === "small" })}
|
||||
>
|
||||
<div className="indicator" />
|
||||
<div className={styles.indicator} />
|
||||
{children}
|
||||
</ReactAriaSwitch>
|
||||
);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user