diff --git a/app/components/Alert.module.css b/app/components/Alert.module.css new file mode 100644 index 000000000..1bef9c87a --- /dev/null +++ b/app/components/Alert.module.css @@ -0,0 +1,56 @@ +.alert { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + border-radius: var(--rounded); + background-color: var(--color-info-low); + color: var(--color-info-high); + font-size: var(--fonts-sm); + font-weight: var(--semi-bold); + gap: var(--s-2); + margin-inline: auto; + padding-block: var(--s-1-5); + padding-inline: var(--s-3) var(--s-4); + text-align: center; + + & > svg { + height: 1.75rem; + fill: var(--color-info); + } +} + +.tiny { + font-size: var(--fonts-xs); + + & > svg { + height: 1.25rem; + } +} + +.warning { + background-color: var(--color-warning-low); + color: var(--color-warning-high); + + & > svg { + fill: var(--color-warning); + } +} + +.error { + background-color: var(--color-error-low); + color: var(--color-error-high); + + & > svg { + fill: var(--color-error); + } +} + +.success { + background-color: var(--color-success-low); + color: var(--color-success-high); + + & > svg { + fill: var(--color-success); + } +} diff --git a/app/components/Alert.tsx b/app/components/Alert.tsx index d5860711b..cb61fccff 100644 --- a/app/components/Alert.tsx +++ b/app/components/Alert.tsx @@ -1,6 +1,7 @@ import clsx from "clsx"; import type * as React from "react"; import { assertUnreachable } from "~/utils/types"; +import styles from "./Alert.module.css"; import { AlertIcon } from "./icons/Alert"; import { CheckmarkIcon } from "./icons/Checkmark"; import { ErrorIcon } from "./icons/Error"; @@ -22,11 +23,11 @@ export function Alert({ }) { return (
{" "} diff --git a/app/components/Avatar.module.css b/app/components/Avatar.module.css new file mode 100644 index 000000000..b58a01434 --- /dev/null +++ b/app/components/Avatar.module.css @@ -0,0 +1,4 @@ +.avatar { + border-radius: 50%; + background-color: var(--color-bg-higher); +} diff --git a/app/components/Avatar.tsx b/app/components/Avatar.tsx index 1be4c682e..710634da9 100644 --- a/app/components/Avatar.tsx +++ b/app/components/Avatar.tsx @@ -2,6 +2,7 @@ import clsx from "clsx"; import * as React from "react"; import type { Tables } from "~/db/tables"; import { BLANK_IMAGE_URL, discordAvatarUrl } from "~/utils/urls"; +import styles from "./Avatar.module.css"; const dimensions = { xxxs: 16, @@ -49,7 +50,7 @@ export function Avatar({ return ( {alt}; + return
; } return ( -
+
+

{header()} {headerSuffix} @@ -135,8 +136,8 @@ function ChartTooltip({ return (
-
- {dataPoint.originalSeries.label} -
-
+
{dataPoint.originalSeries.label}
+
{dataPoint.secondaryValue} {valueSuffix}
diff --git a/app/components/CustomizedColorsInput.module.css b/app/components/CustomizedColorsInput.module.css new file mode 100644 index 000000000..e150f05b9 --- /dev/null +++ b/app/components/CustomizedColorsInput.module.css @@ -0,0 +1,80 @@ +.summary { + padding: var(--s-2) var(--s-3); + border: 2px solid var(--color-border); + border-radius: var(--rounded-sm); + background-color: var(--color-bg); + font-weight: var(--bold); + font-size: var(--fonts-xs); + + & div { + display: inline-flex; + } + + & svg { + width: 24px; + color: var(--color-accent); + position: absolute; + right: 20px; + top: 14px; + } + + & + div { + margin-block-start: var(--s-4); + } +} + +.container { + width: 100%; + font-size: var(--fonts-sm); + font-weight: var(--bold); + padding: var(--s-3); + border: 2px solid var(--color-border); + border-radius: var(--rounded-sm); + background-color: var(--color-bg); + margin-bottom: var(--s-3); + overflow-x: auto; +} + +.grid { + display: grid; + justify-content: space-between; + grid-template-columns: repeat(3, max-content); + gap: var(--s-3); +} + +.table { + width: 100%; + border-spacing: 0; + text-indent: 0; + text-align: left; + font-size: var(--fonts-xs); + + & svg { + width: 1rem; + height: 1rem; + display: inline; + vertical-align: sub; + margin-right: 2px; + } + + & td { + padding-block: var(--s-2); + } + + & tr:last-child td { + border-bottom: none; + padding-bottom: 0; + } +} + +.contrast { + text-wrap-mode: nowrap; +} + +.fail { + color: var(--color-error); +} + +.success { + color: var(--color-success); +} diff --git a/app/components/CustomizedColorsInput.tsx b/app/components/CustomizedColorsInput.tsx index c05b11c86..887b4a35e 100644 --- a/app/components/CustomizedColorsInput.tsx +++ b/app/components/CustomizedColorsInput.tsx @@ -3,6 +3,7 @@ import * as React from "react"; import { useTranslation } from "react-i18next"; import { useDebounce } from "react-use"; import { CUSTOM_CSS_VAR_COLORS } from "~/features/user-page/user-page-constants"; +import styles from "./CustomizedColorsInput.module.css"; import { SendouButton } from "./elements/Button"; import { InfoPopover } from "./InfoPopover"; import { AlertIcon } from "./icons/Alert"; @@ -72,7 +73,7 @@ export function CustomizedColorsInput({ return (
- +
{t("custom.colors.title")}
@@ -86,7 +87,7 @@ export function CustomizedColorsInput({ colorsWithDefaultsFilteredOut(colors, defaultColors), )} /> -
+
{CUSTOM_CSS_VAR_COLORS.filter( (cssVar) => cssVar !== "bg-lightest", ).map((cssVar) => { @@ -138,12 +139,10 @@ export function CustomizedColorsInput({ - +
@@ -160,8 +159,10 @@ export function CustomizedColorsInput({
{t("custom.colors.contrast.first-color")}{t(`custom.colors.${contrast.colors[1]}`)} {contrast.contrast.AA.failed ? ( @@ -173,8 +174,10 @@ export function CustomizedColorsInput({ {contrast.contrast.AAA.failed ? ( diff --git a/app/components/Divider.module.css b/app/components/Divider.module.css new file mode 100644 index 000000000..a61f1a97f --- /dev/null +++ b/app/components/Divider.module.css @@ -0,0 +1,28 @@ +.divider { + display: flex; + width: 100%; + align-items: center; + color: var(--color-text-accent); + font-size: var(--fonts-lg); + text-align: center; + + &::before, + &::after { + flex: 1; + min-width: 1rem; + border-bottom: 2px solid var(--color-bg-high); + content: ""; + } + + &:not(:empty)::before { + margin-right: 0.25em; + } + + &:not(:empty)::after { + margin-left: 0.25em; + } +} + +.smallText { + font-size: var(--fonts-sm); +} diff --git a/app/components/Divider.tsx b/app/components/Divider.tsx index d4d49bbf2..6514a5b39 100644 --- a/app/components/Divider.tsx +++ b/app/components/Divider.tsx @@ -1,4 +1,5 @@ import clsx from "clsx"; +import styles from "./Divider.module.css"; export function Divider({ children, @@ -10,7 +11,11 @@ export function Divider({ smallText?: boolean; }) { return ( -
+
{children}
); diff --git a/app/components/FormErrors.module.css b/app/components/FormErrors.module.css new file mode 100644 index 000000000..b3069c015 --- /dev/null +++ b/app/components/FormErrors.module.css @@ -0,0 +1,7 @@ +.container { + font-size: var(--fonts-sm); + + & > h4 { + color: var(--color-error); + } +} diff --git a/app/components/FormErrors.tsx b/app/components/FormErrors.tsx index c6c8edf94..51b50af0e 100644 --- a/app/components/FormErrors.tsx +++ b/app/components/FormErrors.tsx @@ -1,6 +1,7 @@ import { useActionData } from "@remix-run/react"; import { useTranslation } from "react-i18next"; import type { Namespace } from "~/modules/i18n/resources.server"; +import styles from "./FormErrors.module.css"; export function FormErrors({ namespace }: { namespace: Namespace }) { const { t } = useTranslation(["common", namespace]); @@ -11,7 +12,7 @@ export function FormErrors({ namespace }: { namespace: Namespace }) { } return ( -
+

{t("common:forms.errors.title")}:

    {actionData.errors.map((error) => ( diff --git a/app/components/FormMessage.module.css b/app/components/FormMessage.module.css new file mode 100644 index 000000000..36e79e71c --- /dev/null +++ b/app/components/FormMessage.module.css @@ -0,0 +1,13 @@ +.error { + display: block; + color: var(--color-error); + font-size: var(--fonts-xs); + margin-block-start: var(--label-margin); +} + +.info { + display: block; + color: var(--color-text-high); + font-size: var(--fonts-xs); + margin-block-start: var(--label-margin); +} diff --git a/app/components/FormMessage.tsx b/app/components/FormMessage.tsx index 4e90444f0..9693e4e67 100644 --- a/app/components/FormMessage.tsx +++ b/app/components/FormMessage.tsx @@ -1,5 +1,6 @@ import clsx from "clsx"; import type * as React from "react"; +import styles from "./FormMessage.module.css"; export function FormMessage({ children, @@ -13,7 +14,7 @@ export function FormMessage({ return (
    diff --git a/app/components/Image.module.css b/app/components/Image.module.css new file mode 100644 index 000000000..d877ae152 --- /dev/null +++ b/app/components/Image.module.css @@ -0,0 +1,8 @@ +.tierContainer { + display: grid; +} + +.tierImg { + grid-column: 1; + grid-row: 1; +} diff --git a/app/components/Image.tsx b/app/components/Image.tsx index aea53e143..c28410722 100644 --- a/app/components/Image.tsx +++ b/app/components/Image.tsx @@ -19,6 +19,7 @@ import { TIER_PLUS_URL, tierImageUrl, } from "~/utils/urls"; +import styles from "./Image.module.css"; interface ImageProps { path: string; @@ -229,14 +230,14 @@ export function TierImage({ tier, className, width = 200 }: TierImageProps) { const height = width * 0.8675; return ( -
    +
    {title} {tier.isPlus ? ( {title} ) : null}
    diff --git a/app/components/InfoPopover.module.css b/app/components/InfoPopover.module.css new file mode 100644 index 000000000..83b1b0852 --- /dev/null +++ b/app/components/InfoPopover.module.css @@ -0,0 +1,24 @@ +.trigger { + border: 2px solid var(--color-bg-higher); + border-radius: 100%; + background-color: transparent; + color: var(--color-text); + font-size: var(--fonts-md); + padding: var(--s-0-5); + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + + &:focus-visible { + outline: var(--input-focus-ring); + outline-offset: 1px; + } +} + +.triggerTiny { + width: 20px; + height: 20px; + font-size: var(--fonts-xs); +} diff --git a/app/components/InfoPopover.tsx b/app/components/InfoPopover.tsx index 7c8e56e5d..c520ffa48 100644 --- a/app/components/InfoPopover.tsx +++ b/app/components/InfoPopover.tsx @@ -1,6 +1,7 @@ import clsx from "clsx"; import { Button } from "react-aria-components"; import { SendouPopover } from "./elements/Popover"; +import styles from "./InfoPopover.module.css"; export function InfoPopover({ children, @@ -15,14 +16,9 @@ export function InfoPopover({ ? diff --git a/app/components/Input.module.css b/app/components/Input.module.css new file mode 100644 index 000000000..1388dba84 --- /dev/null +++ b/app/components/Input.module.css @@ -0,0 +1,60 @@ +.container { + display: flex; + font-size: var(--fonts-sm); + outline: none; + line-height: var(--input-line-height); + border: 2px solid var(--color-border); + border-radius: var(--rounded-sm); + background-color: var(--color-bg); + height: var(--input-height); + width: 100%; + + & svg { + color: var(--color-text-high); + height: calc(var(--input-height) / 2); + margin: auto; + margin-right: 15px; + } + + &:has(input:user-invalid) { + outline: var(--input-focus-ring-error); + outline-offset: 1px; + } + + &:focus-within { + outline: var(--input-focus-ring); + outline-offset: 1px; + } + + input { + border-radius: var(--rounded-sm); + padding: 0 var(--input-padding-inline); + outline: none; + width: 100%; + background-color: inherit; + border: none; + + &::placeholder { + color: var(--color-text-high); + } + } +} + +.readOnly { + pointer-events: none; + cursor: not-allowed; + opacity: 0.5; + outline: none; +} + +.addon { + display: grid; + border-radius: var(--rounded-xs) 0 0 var(--rounded-xs); + background-color: var(--color-bg-high); + color: var(--color-text-high); + font-size: var(--fonts-xs); + font-weight: var(--semi-bold); + padding-inline: var(--s-2); + place-items: center; + white-space: nowrap; +} diff --git a/app/components/Input.tsx b/app/components/Input.tsx index 40e0a8903..6c4b57978 100644 --- a/app/components/Input.tsx +++ b/app/components/Input.tsx @@ -1,4 +1,5 @@ import clsx from "clsx"; +import styles from "./Input.module.css"; export function Input({ name, @@ -47,11 +48,11 @@ export function Input({ }) { return (
    - {leftAddon ?
    {leftAddon}
    : null} + {leftAddon ?
    {leftAddon}
    : null} label { + margin: 0; + } +} + +.value { + color: var(--color-text-high); + font-size: var(--fonts-xxs); + margin-block-start: -5px; +} + +.valueWarning { + color: var(--color-warning); +} + +.valueError { + color: var(--color-error); +} diff --git a/app/components/Label.tsx b/app/components/Label.tsx index 89382874b..d24829ed3 100644 --- a/app/components/Label.tsx +++ b/app/components/Label.tsx @@ -1,4 +1,5 @@ import clsx from "clsx"; +import styles from "./Label.module.css"; type LabelProps = Pick< React.DetailedHTMLProps< @@ -27,12 +28,12 @@ export function Label({ spaced = true, }: LabelProps) { return ( -
    +
    {valueLimits ? ( -
    +
    {valueLimits.current}/{valueLimits.max}
    ) : null} @@ -40,9 +41,12 @@ export function Label({ ); } -function lengthWarning(valueLimits: NonNullable) { - if (valueLimits.current > valueLimits.max) return "error"; - if (valueLimits.current / valueLimits.max >= 0.9) return "warning"; +function lengthWarning( + valueLimits: NonNullable, + s: typeof styles, +) { + if (valueLimits.current > valueLimits.max) return s.valueError; + if (valueLimits.current / valueLimits.max >= 0.9) return s.valueWarning; return; } diff --git a/app/components/Pagination.module.css b/app/components/Pagination.module.css new file mode 100644 index 000000000..88e6be059 --- /dev/null +++ b/app/components/Pagination.module.css @@ -0,0 +1,44 @@ +.container { + display: grid; + grid-template-columns: auto auto auto; + gap: var(--s-2); + align-items: center; + justify-items: center; + justify-content: center; +} + +.dots { + display: none; + align-items: center; + justify-content: center; + gap: var(--s-1); + flex-wrap: wrap; +} + +.dot { + width: 0.6rem; + height: 0.6rem; + border-radius: 50%; + background-color: var(--color-bg-higher); + transition: all 0.2s ease; +} + +.dotActive { + background-color: var(--color-text-accent); +} + +.pageCount { + font-size: var(--fonts-sm); + font-weight: var(--bold); + color: var(--color-accent); +} + +@media screen and (min-width: 640px) { + .dots { + display: flex; + } + + .pageCount { + display: none; + } +} diff --git a/app/components/Pagination.tsx b/app/components/Pagination.tsx index be5ed64fa..429a62a47 100644 --- a/app/components/Pagination.tsx +++ b/app/components/Pagination.tsx @@ -3,6 +3,7 @@ import { SendouButton } from "~/components/elements/Button"; import { ArrowLeftIcon } from "~/components/icons/ArrowLeft"; import { ArrowRightIcon } from "~/components/icons/ArrowRight"; import { nullFilledArray } from "~/utils/arrays"; +import styles from "./Pagination.module.css"; export function Pagination({ currentPage, @@ -18,7 +19,7 @@ export function Pagination({ setPage: (page: number) => void; }) { return ( -
    +
    } variant="outlined" @@ -27,19 +28,19 @@ export function Pagination({ onPress={previousPage} aria-label="Previous page" /> -
    +
    {nullFilledArray(pagesCount).map((_, i) => ( // biome-ignore lint/a11y/noStaticElementInteractions: Biome v2 migration
    setPage(i + 1)} /> ))}
    -
    +
    {currentPage}/{pagesCount}
    div { + padding: var(--s-2); + border-radius: var(--rounded); + background-color: var(--color-bg); + } + + & > h2 { + color: var(--color-text-high); + font-size: var(--fonts-md); + } +} diff --git a/app/components/Section.tsx b/app/components/Section.tsx index 2e4e6bfd3..eb121e319 100644 --- a/app/components/Section.tsx +++ b/app/components/Section.tsx @@ -1,3 +1,5 @@ +import styles from "./Section.module.css"; + export function Section({ title, children, @@ -8,7 +10,7 @@ export function Section({ className?: string; }) { return ( -
    +
    {title &&

    {title}

    }
    {children}
    diff --git a/app/components/SubNav.module.css b/app/components/SubNav.module.css new file mode 100644 index 000000000..8d0bdf71f --- /dev/null +++ b/app/components/SubNav.module.css @@ -0,0 +1,63 @@ +.container { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: var(--s-4); + margin-block-end: var(--s-8); +} + +.secondary { + margin-block-end: var(--s-2); +} + +.linkContainer { + display: flex; + max-width: 110px; + flex: 1; + flex-direction: column; + align-items: center; + color: var(--color-text); + gap: var(--s-1-5); +} + +.linkContainer.active { + color: var(--color-text-accent); +} + +.link { + width: 100%; + padding: var(--s-1) var(--s-4); + border-radius: var(--rounded); + background-color: var(--color-bg-high); + cursor: pointer; + font-size: var(--fonts-xs); + text-align: center; + white-space: nowrap; +} + +.linkSecondary { + font-size: var(--fonts-xxs); + padding: var(--s-0-5) var(--s-2); + background-color: var(--color-bg-high); +} + +.container.compact .link { + padding: var(--s-1) var(--s-2); +} + +.borderGuy { + width: 78%; + height: 3px; + border-radius: var(--rounded); + background-color: var(--color-bg-higher); + visibility: hidden; +} + +.borderGuySecondary { + height: 2.5px; + background-color: var(--color-bg-high); +} + +.linkContainer.active > .borderGuy { + visibility: initial; +} diff --git a/app/components/SubNav.tsx b/app/components/SubNav.tsx index 2fe1e6da1..ed0b82b23 100644 --- a/app/components/SubNav.tsx +++ b/app/components/SubNav.tsx @@ -2,6 +2,7 @@ import type { LinkProps } from "@remix-run/react"; import { NavLink } from "@remix-run/react"; import clsx from "clsx"; import type * as React from "react"; +import styles from "./SubNav.module.css"; export function SubNav({ children, @@ -13,8 +14,8 @@ export function SubNav({ return (