From 37b68012dc211efe475c54abdf7325c3891c424a Mon Sep 17 00:00:00 2001 From: Jessica 'jynnie' Tang Date: Mon, 27 Feb 2023 23:19:47 -0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=81=20Someone's=20PC=20(#18)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO | 16 +- components/AddPokemon/AddPokemon.tsx | 2 +- components/AddPokemon/Form.tsx | 2 +- components/AddToTimeline/Form.tsx | 9 +- components/BoxView/BoxFilters.tsx | 181 +++++++++++++++ components/BoxView/BoxPokemon.tsx | 50 +++++ components/BoxView/BoxView.tsx | 248 +++++++++++++++++++++ components/BoxView/TeamPokemon.tsx | 46 ++++ components/MovePokemon/index.tsx | 2 +- components/Pokemon/Pokemon.tsx | 6 +- components/Pokemon/PokemonBoxModal.tsx | 100 +++++++++ components/Pokemon/PokemonModal.tsx | 14 +- components/PokemonIcon.tsx | 22 +- components/RunHome.tsx | 26 +-- components/Summary/BadgeBox.tsx | 2 + components/WhatsNew.tsx | 98 ++++++++ components/ui-library/EditablePokemon.tsx | 2 +- components/ui-library/SearchableSelect.tsx | 47 ++-- hooks/useAddNewLocation.ts | 2 + hooks/useAddPokemons.ts | 58 +++++ hooks/useBackfillPokemon.ts | 41 +++- hooks/useLocalStorage.ts | 43 ++++ pages/newRun.tsx | 2 +- pages/run/[id].tsx | 79 ++----- public/PokeballLight.svg | 3 + styles/Box.module.scss | 101 +++++++++ styles/Pokemon.module.scss | 10 +- styles/Run.module.scss | 8 +- styles/Summary.module.scss | 1 - styles/Timeline.module.scss | 2 +- styles/WhatsNew.module.scss | 55 +++++ styles/globals.scss | 18 +- utils/utils.ts | 4 + 33 files changed, 1174 insertions(+), 126 deletions(-) create mode 100644 components/BoxView/BoxFilters.tsx create mode 100644 components/BoxView/BoxPokemon.tsx create mode 100644 components/BoxView/BoxView.tsx create mode 100644 components/BoxView/TeamPokemon.tsx create mode 100644 components/Pokemon/PokemonBoxModal.tsx create mode 100644 components/WhatsNew.tsx create mode 100644 hooks/useAddPokemons.ts create mode 100644 hooks/useLocalStorage.ts create mode 100644 public/PokeballLight.svg create mode 100644 styles/Box.module.scss create mode 100644 styles/WhatsNew.module.scss diff --git a/TODO b/TODO index ab17b69..3783dbb 100644 --- a/TODO +++ b/TODO @@ -2,15 +2,25 @@ 🌶🌶🌶 Super cool -- [ ] New region! - [ ] Timeline: Inline editing -- [ ] Box view? TL location in the bottom -- [ ] Box: Drag and drop between categories to move + - [ ] Locations + - [ ] Origin names + - [ ] Nicknames +- [x] Box View + - [x] Drag and drop between categories to move + - [x] Edit pokemon from view + - [x] Adding to team needs to make sure not full + - [x] Add new Pokemon + - [x] Styling + - [x] Show current location? +- [ ] Shared type rule (either present suggested parties or a toggle to enforce rule) 🌶🌶 Would improve most experiences or could be cool - [ ] Skeletons while loading +- [ ] Mobile friendly - [ ] Cooler summary view styles +- [ ] New region! (Waiting on pokeapi to update tbh) 🌶 Should have, but low value add rn diff --git a/components/AddPokemon/AddPokemon.tsx b/components/AddPokemon/AddPokemon.tsx index aebc983..38ac4f1 100644 --- a/components/AddPokemon/AddPokemon.tsx +++ b/components/AddPokemon/AddPokemon.tsx @@ -46,7 +46,7 @@ export function AddPokemon({ interactiveBorder={10} onClickOutside={() => setShowForm(false)} > -
+
}>
+ + + + + ); +} + +function AddPokemonModal({ + visible, + onCancel, +}: { + visible: boolean; + onCancel: () => void; +}) { + const router = useRouter(); + const { id: rawId } = router.query; + const id = Array.isArray(rawId) ? rawId[0] : rawId; + + const region = useRegion(id); + const regionData = useRegionData(region); + const allLocations = regionData?.locations || []; + + const { allPokemon } = React.useContext(RunContext); + const players = usePlayersArray(); + + const [newLocation, setLocation] = useState(""); + const [pokemonNames, setPokemonNames] = useState( + Array(players.length).fill(""), + ); + const [nicknames, setNicknames] = useState( + Array(players.length).fill(""), + ); + + const addPokemon = useAddPokemonAndLocation(); + + async function addNew() { + await addPokemon( + newLocation, + players.map((p) => p.id), + pokemonNames, + nicknames, + ); + onCancel(); + } + + function updatePokemonName(index: number, name: string) { + const newPokemonNames = [...pokemonNames]; + newPokemonNames[index] = name; + setPokemonNames(newPokemonNames); + } + + function updateNickname(index: number, name: string) { + const newNicknames = [...nicknames]; + newNicknames[index] = name; + setNicknames(newNicknames); + } + + return ( + + <> +

Add Pokémon

+ +
+ + ({ + value: l.name, + label: capitalize(cleanName(l.name)), + })), + ]} + value={newLocation} + onChange={(value?: string) => setLocation(value || "")} + placeholder="Add New Location" + allowCustom + /> +
+ +
+ {players?.map((player, index) => ( +
+

{player.name}

+ + + updatePokemonName(index, value || "") + } + value={pokemonNames[index]} + placeholder="Select a Pokémon" + options={allPokemon.map((p) => ({ + value: p.name, + label: p.name, + }))} + /> + updateNickname(index, e.target.value)} + /> +
+ ))} +
+
+ +
+ +
+ ); +} diff --git a/components/BoxView/BoxPokemon.tsx b/components/BoxView/BoxPokemon.tsx new file mode 100644 index 0000000..86c4368 --- /dev/null +++ b/components/BoxView/BoxPokemon.tsx @@ -0,0 +1,50 @@ +import { PokemonBoxModal } from "components/Pokemon/PokemonBoxModal"; +import PokemonImage from "components/PokemonImage"; +import { Data } from "components/Timeline/Timeline"; +import { TooltipContent } from "components/ui-library/TooltipContent"; +import React from "react"; +import styles from "styles/Box.module.scss"; + +import Tippy from "@tippyjs/react"; + +export function BoxPokemon({ data }: { data: Data }) { + const [showModal, setShowModal] = React.useState(false); + + const handleOpenModal = () => setShowModal(true); + const handleCloseModal = () => setShowModal(false); + + return ( + <> + + +
+ + {data.pokemon.map((p) => p.nickname || "?").join(" & ")} + + } + delay={[500, 0]} + > +
+ {data.pokemon.map((p, i) => ( + + ))} +
+
+
+ + ); +} diff --git a/components/BoxView/BoxView.tsx b/components/BoxView/BoxView.tsx new file mode 100644 index 0000000..2162d21 --- /dev/null +++ b/components/BoxView/BoxView.tsx @@ -0,0 +1,248 @@ +import classNames from "classnames"; +import { useFilters } from "components/Timeline/useFilters"; +import { useTimelineData } from "components/Timeline/useTimelineData"; +import { useMovePokemon } from "hooks/useMovePokemon.1"; +import { useRunChild } from "hooks/useRun"; +import { useTimelineLocations } from "hooks/useTimelineLocations"; +import { Player, PokemonLocation } from "models"; +import React, { useMemo } from "react"; +import { + DragDropContext, + Draggable, + DropResult, + Droppable, +} from "react-beautiful-dnd"; +import { toast } from "react-toastify"; +import styles from "styles/Box.module.scss"; +import { getLastItem } from "utils/getLastItem"; + +import { BoxFilters } from "./BoxFilters"; +import { BoxPokemon } from "./BoxPokemon"; +import { TeamPokemon } from "./TeamPokemon"; + +export function BoxView() { + const { team, box, grave, daycare, onFilterChange } = usePokemon(); + const players = useRunChild>("players", {}); + const movePokemon = useMovePokemon(players); + + const timelineLocations = useTimelineLocations(); + const latestLocation = getLastItem(timelineLocations); + + async function onDragEnd(result: DropResult) { + // dropped outside the list + if (!result.destination) return; + + // same location (we're not doing index rearrangement) + if (result.destination.droppableId === result.source.droppableId) { + return; + } + + // Move pokemon from one place to another depending on the drop location + const pokemon = result.draggableId; // Draggable id is the location key + const to = result.destination.droppableId as PokemonLocation; + + // Can't add to team if it's already full + if (team.length >= 6 && to === "team") { + toast.warn("You already have 6 Pokémon on your team!"); + return; + } + + movePokemon(pokemon, latestLocation.key, to); + } + + return ( +
+ + + +
+ + {(p) => ( +
+
+ Team +
+
+ {team?.map((d, i) => ( + + {(p) => ( + + + + )} + + ))} +
+ {!team?.length &&

None

} +
+ )} +
+ +
+ + {(p) => ( +
+
Box
+
+ {box?.map((d, i) => ( + + {(p) => ( + + + + )} + + ))} +
+ {!box?.length &&

None

} +
+ )} +
+ + + {(p) => ( +
+
Daycare
+
+ {daycare?.map((d, i) => ( + + {(p) => ( + + + + )} + + ))} + {!daycare?.length &&

None

} +
+
+ )} +
+ + + {(p) => ( +
+
Grave
+
+ {grave?.map((d, i) => ( + + {(p) => ( + + + + )} + + ))} +
+ {!grave?.length &&

None

} +
+ )} +
+
+
+
+
+ ); +} + +function usePokemon() { + const { allDataArr } = useTimelineData(); + const { filteredData, onFilterChange } = useFilters(allDataArr); + + const team = useMemo( + () => + (filteredData || allDataArr)?.filter( + (d) => d.pokemonLocation === PokemonLocation.team, + ), + [filteredData, allDataArr], + ); + + const box = useMemo( + () => + (filteredData || allDataArr)?.filter( + (d) => d.pokemonLocation === PokemonLocation.box, + ), + [filteredData, allDataArr], + ); + + const grave = useMemo( + () => + (filteredData || allDataArr)?.filter( + (d) => d.pokemonLocation === PokemonLocation.grave && d.pokemonNames, + ), + [filteredData, allDataArr], + ); + + const daycare = useMemo( + () => + (filteredData || allDataArr)?.filter( + (d) => d.pokemonLocation === PokemonLocation.daycare, + ), + [filteredData, allDataArr], + ); + return { team, box, grave, daycare, onFilterChange }; +} diff --git a/components/BoxView/TeamPokemon.tsx b/components/BoxView/TeamPokemon.tsx new file mode 100644 index 0000000..7c8c96d --- /dev/null +++ b/components/BoxView/TeamPokemon.tsx @@ -0,0 +1,46 @@ +import { PokemonBoxModal } from "components/Pokemon/PokemonBoxModal"; +import PokemonImage from "components/PokemonImage"; +import { Data } from "components/Timeline/Timeline"; +import React from "react"; +import styles from "styles/Box.module.scss"; +import { cleanName } from "utils/utils"; + +export function TeamPokemon({ data }: { data: Data }) { + const [showModal, setShowModal] = React.useState(false); + + const handleOpenModal = () => setShowModal(true); + const handleCloseModal = () => setShowModal(false); + + return ( + <> + + +
+
+
{cleanName(data.location.name)}
+
+ {cleanName(data.pokemon.map((p) => p.nickname || "?").join(" & "))} +
+
+ +
+ {data.pokemon.map((p, i) => ( + + ))} +
+
+ + ); +} diff --git a/components/MovePokemon/index.tsx b/components/MovePokemon/index.tsx index 2e9347f..d71b289 100644 --- a/components/MovePokemon/index.tsx +++ b/components/MovePokemon/index.tsx @@ -79,7 +79,7 @@ export function MovePokemon({ * At setLocation(value)} + onChange={(value) => setLocation(value || "")} value={location} placeholder="Select the location of this event" options={timelineLocations?.map((l) => ({ diff --git a/components/Pokemon/Pokemon.tsx b/components/Pokemon/Pokemon.tsx index bb86019..670a0f8 100644 --- a/components/Pokemon/Pokemon.tsx +++ b/components/Pokemon/Pokemon.tsx @@ -30,7 +30,11 @@ export function TimelinePokemon({ }} /> - + ); } diff --git a/components/Pokemon/PokemonBoxModal.tsx b/components/Pokemon/PokemonBoxModal.tsx new file mode 100644 index 0000000..398d509 --- /dev/null +++ b/components/Pokemon/PokemonBoxModal.tsx @@ -0,0 +1,100 @@ +import PLTag from "components/LocationSummary/PLTag"; +import PokemonImage from "components/PokemonImage"; +import { Data } from "components/Timeline/Timeline"; +import { EditablePokemon } from "components/ui-library/EditablePokemon"; +import { EditableText } from "components/ui-library/EditableText"; +import { Modal } from "components/ui-library/Modal"; +import { useBackfillPokemons } from "hooks/useBackfillPokemon"; +import { usePlayersArray } from "hooks/usePlayersArray"; +import { PokemonLocation } from "models"; +import styles from "styles/Pokemon.module.scss"; +import { capitalize, cleanName } from "utils/utils"; + +export function PokemonBoxModal({ + data, + showModal, + onCancel, +}: { + data: Data; + showModal: boolean; + onCancel: () => void; +}) { + const players = usePlayersArray(); + const backfillPokemon = useBackfillPokemons(data.location.key); + const pokemons = data.pokemon; + + function handleFinish( + playerId: string, + pokemonName: string, + nickname: string, + location: PokemonLocation, + ): void { + backfillPokemon(playerId, pokemonName, nickname, location); + } + + return ( + + <> +
+ Caught in {capitalize(cleanName(data.location.name || "?"))} + +
+ + {pokemons.map((pokemon) => ( +
+
+ +
+ +
+

+ {players.find((p) => p.id === pokemon.playerId)?.name}'s +

+ + + {cleanName(pokemon?.nickname) || "Unspecified Nickname"} + + } + value={cleanName(pokemon?.nickname)} + onChange={(nickname: string) => + handleFinish( + pokemon.playerId, + pokemon.name, + nickname, + pokemon.location, + ) + } + /> + + {cleanName(pokemon?.name) || "Unspecified Pokémon"} + + } + value={cleanName(pokemon?.name)} + onChange={(name?: string) => + name && + handleFinish( + pokemon.playerId, + name, + pokemon?.nickname, + pokemon.location, + ) + } + /> +
+
+ ))} + +
+ ); +} diff --git a/components/Pokemon/PokemonModal.tsx b/components/Pokemon/PokemonModal.tsx index 3f5efab..67dc9d1 100644 --- a/components/Pokemon/PokemonModal.tsx +++ b/components/Pokemon/PokemonModal.tsx @@ -55,7 +55,9 @@ function PokemonModal({ onCancel={onCancel} >
- +
+ +
{cleanName(pokemon?.nickname)} +

+ {cleanName(pokemon?.nickname) || "Unspecified Nickname"} +

} value={cleanName(pokemon?.nickname)} onChange={(name: string) => handleFinish(pokemon.name, name)} /> {cleanName(pokemon?.name)}} + display={ +

+ {cleanName(pokemon?.name) || "Unspecified Pokémon"} +

+ } value={cleanName(pokemon?.name)} onChange={(name?: string) => name && handleFinish(name, pokemon?.nickname) diff --git a/components/PokemonIcon.tsx b/components/PokemonIcon.tsx index 7a71e1d..bbcc984 100644 --- a/components/PokemonIcon.tsx +++ b/components/PokemonIcon.tsx @@ -1,3 +1,4 @@ +import classNames from "classnames"; import PokemonImage from "components/PokemonImage"; import type { IPokemon } from "models"; import React from "react"; @@ -10,12 +11,14 @@ import Tooltip from "@tippyjs/react"; import { TooltipContent } from "./ui-library/TooltipContent"; export function PokemonIcon({ + className, pokemon, onClick, - width = 32, - height = 32, + width = 36, + height = 36, backgroundColor, }: { + className?: string; pokemon: IPokemon; onClick?: () => void; width?: number; @@ -28,7 +31,7 @@ export function PokemonIcon({ const { data } = usePalette(src || ""); const avatarStyle = { backgroundColor: - backgroundColor || data?.vibrant || "var(--midnight-primary)", + backgroundColor || data?.vibrant + "36" || "var(--midnight-primary)", }; return ( @@ -37,16 +40,21 @@ export function PokemonIcon({ placement="top" >
- +
diff --git a/components/RunHome.tsx b/components/RunHome.tsx index 7880d7a..af33727 100644 --- a/components/RunHome.tsx +++ b/components/RunHome.tsx @@ -1,13 +1,16 @@ import classNames from "classnames"; -import Summary from "components/Summary"; -import Timeline from "components/Timeline"; import { useGameName } from "hooks/useGameName"; +import { useLocalStorage } from "hooks/useLocalStorage"; import { usePlayersArray } from "hooks/usePlayersArray"; import type { MapLocation } from "models"; import Image from "next/image"; -import React, { useState } from "react"; +import React from "react"; import styles from "styles/Run.module.scss"; +import { BoxView } from "./BoxView/BoxView"; +import Summary from "./Summary"; +import Timeline from "./Timeline"; + interface Props { id: string; allBadges: string[]; @@ -19,7 +22,7 @@ function RunHome({ region, allBadges, allLocations }: Props) { const playerArr = usePlayersArray(); const game = useGameName(); - const [viewingTab, setViewingTab] = useState(0); + const [viewingTab, setViewingTab] = useLocalStorage("runTab", 0); return (

{game} Soullocke

- {/*
codename: {id}
*/} -
-
- {["Timeline", "Summary"].map((t, k) => ( +
+
+ {["Timeline", "Box", "Summary"].map((t, k) => (
+ +
+
diff --git a/components/Summary/BadgeBox.tsx b/components/Summary/BadgeBox.tsx index bb952a2..d80fb64 100644 --- a/components/Summary/BadgeBox.tsx +++ b/components/Summary/BadgeBox.tsx @@ -17,6 +17,8 @@ import Tippy from "@tippyjs/react"; export function BadgeBox({ allBadges }: { allBadges: string[] }) { const earnedBadges = useEarnedBadges(); + if (allBadges.length === 0) return null; + return (
{allBadges.map((badge) => ( diff --git a/components/WhatsNew.tsx b/components/WhatsNew.tsx new file mode 100644 index 0000000..2bb1bef --- /dev/null +++ b/components/WhatsNew.tsx @@ -0,0 +1,98 @@ +import classNames from "classnames"; +import mixpanel from "mixpanel-browser"; +import { Coffee, GitHub, Instagram, X } from "react-feather"; +import styles from "styles/WhatsNew.module.scss"; + +import { Button } from "./ui-library/Button"; + +export function WhatsNew({ + visible, + cancel, +}: { + visible: boolean; + cancel: () => void; +}) { + return ( +
+
diff --git a/public/PokeballLight.svg b/public/PokeballLight.svg new file mode 100644 index 0000000..1788a85 --- /dev/null +++ b/public/PokeballLight.svg @@ -0,0 +1,3 @@ + + + diff --git a/styles/Box.module.scss b/styles/Box.module.scss new file mode 100644 index 0000000..d01f33e --- /dev/null +++ b/styles/Box.module.scss @@ -0,0 +1,101 @@ +.filters { + @apply mb-4 gap-2; + + display: grid; + grid-template-columns: 1fr 2fr 1fr; + + .location { + @apply flex items-center justify-center capitalize; + + color: var(--text-caption); + font-family: Teko; + font-size: 1.1rem; + text-transform: uppercase; + letter-spacing: 0.08em; + } +} + +.page { + max-width: var(--max-width); + width: calc(100vw - 4rem); +} + +.pokemonContainer { + @apply flex gap-10 mt-8; +} + +.teamColumn { + @apply flex flex-col gap-2; + + .header { + @apply uppercase text-xl; + } + + .container { + @apply flex flex-col gap-4; + + width: 260px; + } + + .pokemon { + @apply py-2 px-6 shadow-lg flex items-center relative; + + border-radius: 48px; + height: 62px; + width: 260px; + background: linear-gradient( + 118deg, + var(--midnight-primary), + var(--midnight-primary) 70%, + var(--midnight-hover) 70% + ); + + transition: filter 0.2s ease-in-out; + + &:hover { + filter: brightness(1.4); + } + + .name { + @apply text-lg capitalize font-bold truncate; + + line-height: 1.1em; + max-width: 232px; + } + + .images { + @apply flex items-center absolute right-4; + } + } +} + +.nonTeamColumn { + @apply flex flex-col gap-10; + + .section { + @apply flex flex-col gap-2; + min-height: 95.5px; + } + + .container { + @apply flex flex-wrap gap-4; + } + + .pokemon { + @apply rounded-lg flex justify-center items-center bg-contain; + + width: 64px; + height: 64px; + background-image: url("/PokeballLight.svg"); + + transition: filter 0.2s ease-in-out; + + &:hover { + filter: brightness(1.4); + } + + .images { + @apply flex items-center; + } + } +} diff --git a/styles/Pokemon.module.scss b/styles/Pokemon.module.scss index 0a52cb0..257cb2c 100644 --- a/styles/Pokemon.module.scss +++ b/styles/Pokemon.module.scss @@ -35,8 +35,7 @@ position: relative; img { - max-width: 100%; - max-height: 100%; + position: absolute; } } } @@ -62,6 +61,13 @@ width: 80%; &Img { + @apply flex items-center justify-center; + + width: 96px; + height: 96px; + color: var(--text-caption); + font-size: 1.5rem; + image-rendering: crisp-edges; image-rendering: pixelated; } diff --git a/styles/Run.module.scss b/styles/Run.module.scss index 4c883f9..4030ba4 100644 --- a/styles/Run.module.scss +++ b/styles/Run.module.scss @@ -32,7 +32,13 @@ } .content { - max-width: 720px; + @apply mb-4 flex flex-col items-center justify-center gap-2; + + max-width: var(--max-width); +} + +.tabContainer { + @apply flex gap-6 mt-4 mb-8; } .tab { diff --git a/styles/Summary.module.scss b/styles/Summary.module.scss index f0e1579..86ee8b4 100644 --- a/styles/Summary.module.scss +++ b/styles/Summary.module.scss @@ -129,7 +129,6 @@ align-items: flex-end; .gravePokemon { flex-direction: row-reverse; - justify-content: flex-end; } } diff --git a/styles/Timeline.module.scss b/styles/Timeline.module.scss index c452009..cb33b1f 100644 --- a/styles/Timeline.module.scss +++ b/styles/Timeline.module.scss @@ -2,7 +2,7 @@ @apply mt-6; width: calc(100vw - 4rem); - max-width: 720px; + max-width: var(--max-width); border-collapse: separate; border-spacing: 0 0.4rem; diff --git a/styles/WhatsNew.module.scss b/styles/WhatsNew.module.scss new file mode 100644 index 0000000..bfb3086 --- /dev/null +++ b/styles/WhatsNew.module.scss @@ -0,0 +1,55 @@ +.container { + position: fixed; + top: 0; + bottom: 0; + right: -360px; + width: 360px; + z-index: 99999; + + overflow: auto; + + background-color: var(--midnight-primary); + box-shadow: 0 0 10px 2px rgba(2, 20, 46, 0.5); + + transition: right 0.3s ease-in-out; + + @apply py-4 px-6; + + &.visible { + right: 0px; + } + + .x { + position: absolute; + top: 8px; + right: 8px; + } + + h3 { + color: var(--text-caption); + } + + h5 { + @apply m-0; + color: var(--text-caption); + font-family: Teko; + font-size: 1.2rem; + font-weight: 400; + text-transform: uppercase; + letter-spacing: 0.08em; + } + + h4 { + @apply mt-0 mb-2; + font-size: 1.3rem; + font-weight: 500; + } + + ul { + display: flex; + flex-direction: column; + @apply gap-2; + + padding-left: 2rem; + } +} diff --git a/styles/globals.scss b/styles/globals.scss index bad3372..330a6c9 100644 --- a/styles/globals.scss +++ b/styles/globals.scss @@ -1,6 +1,8 @@ @tailwind utilities; :root { + --max-width: 800px; + --text-primary: #ffffff; --text-caption: #f8fafc9a; --text-disabled: #f8fafc4b; @@ -71,7 +73,7 @@ body { padding: 0; margin: 0; font-family: var(--font); - font-size: 13px; + font-size: 14px; overflow-x: hidden; overflow-y: auto; @@ -115,6 +117,18 @@ p { } } +.p { + &-blue { + color: var(--blue-primary); + } + &-pink { + color: var(--pink-primary); + } + &-yellow { + color: var(--yellow-primary); + } +} + a { --primary: var(--purple-primary); --primary-text: var(--purple-primary-text); @@ -303,7 +317,7 @@ button { input, textarea { background: transparent; - padding: 4px 8px; + padding: 8px 12px; border: 1px solid var(--grey-hover); border-radius: 4px; font-size: 1rem; diff --git a/utils/utils.ts b/utils/utils.ts index 8abb4a8..cb00794 100644 --- a/utils/utils.ts +++ b/utils/utils.ts @@ -1,3 +1,7 @@ +export function capitalize(str: string): string { + return str.replace(/\b\w/g, (match) => match.toUpperCase()); +} + export function cleanName(name: string = "") { return name.replace(/(_|-)/g, " "); }