diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx index d4329cc..3328046 100644 --- a/src/components/Icons.tsx +++ b/src/components/Icons.tsx @@ -7,7 +7,7 @@ export const CornerDownLeft: React.FC<{ className?: string }> = ({ xmlns="http://www.w3.org/2000/svg" className={className} > - + = ({ ); -export const Share: React.FC<{ className?: string }> = ({ className }) => ( +export const Share: React.FC<{ className?: string; onClick: () => void }> = ({ + className, + onClick, +}) => ( diff --git a/src/components/More.tsx b/src/components/More.tsx index c9ae514..b708f6f 100644 --- a/src/components/More.tsx +++ b/src/components/More.tsx @@ -1,10 +1,11 @@ import { useState } from "react"; -import type { ReactNode } from "react"; +import clsx from "clsx"; import { bestRegistrationUrl } from "../vote_gov"; -import { ArrowDown, CornerDownLeft } from "./Icons"; +import { ArrowDown, CornerDownLeft, Share } from "./Icons"; import { STATE_NAMES } from "../states"; import type { State } from "../states"; +import { browserSupportsShare, defaultShare } from "./ShareButton"; /** Countmore power rankings */ const POWER_RANKINGS: { [st: string]: number } = { @@ -30,76 +31,6 @@ const powerRanking = (st: string): number => POWER_RANKINGS[st] || 0; /** Which state is most impactful to vote in for the 2024 presidential election? */ type StateSelection = "home" | "school" | "toss-up"; -/** Selection description */ -const SELECTION_DESCRIPTION: { - [sel in StateSelection]: (homeSt: State, schoolSt: State) => ReactNode; -} = { - home: (homeSt: State, __: State) => ( -
-
- Your vote counts more in{" "} - {STATE_NAMES[homeSt]}. -
-

Put some explanatory text here.

- {bestRegistrationUrl(homeSt) && ( - - Register to vote - - )} -
- ), - school: (_: State, schoolSt: State) => ( -
-
- Your vote counts more in{" "} - {STATE_NAMES[schoolSt]}. -
-

Put some explanatory text here.

- {bestRegistrationUrl(schoolSt) && ( - - Register to vote - - )} -
- ), - "toss-up": (homeSt: State, schoolSt: State) => ( -
-
- Your vote counts the same in{" "} - {STATE_NAMES[homeSt]} and{" "} - {STATE_NAMES[schoolSt]}. -
-

Put some explanatory text here.

- {bestRegistrationUrl(homeSt) && ( - - Register to vote in {STATE_NAMES[homeSt]} - - )}{" "} - {bestRegistrationUrl(schoolSt) && ( - - Register to vote in {STATE_NAMES[schoolSt]} - - )} -
- ), -}; - /** Return the state selection for a state */ const stateSelection = (homeSt: string, schoolSt: string): StateSelection => { const homeRank = powerRanking(homeSt); @@ -157,9 +88,10 @@ const SelectStates: React.FC<{ className="text-black invalid:text-gray-400 w-full rounded-none flex-grow font-cabinet font-extrabold text-[24px] leading-[32px] appearance-none bg-transparent border-b-2 border-black" value={homeSt} required + defaultValue="" onChange={(e) => setHomeSt(e.target.value as State)} > - {Object.entries(STATE_NAMES).map(([st, name]) => ( @@ -186,9 +118,10 @@ const SelectStates: React.FC<{ className="text-black invalid:text-gray-400 w-full rounded-none flex-grow font-cabinet font-extrabold text-[24px] leading-[32px] appearance-none bg-transparent border-b-2 border-black focus:ring-hover" value={schoolSt} required + defaultValue="" onChange={(e) => setSchoolSt(e.target.value as State)} > - {Object.entries(STATE_NAMES).map(([st, name]) => ( @@ -230,7 +163,83 @@ const DescribeSelection: React.FC<{ result: StateSelectionResult }> = ({ result, }) => { const { selection, homeSt, schoolSt } = result; - return
{SELECTION_DESCRIPTION[selection](homeSt, schoolSt)}
; + + return ( +
+
+ {selection !== "toss-up" && ( + <> + Your vote counts more in{" "} + + {STATE_NAMES[selection === "home" ? homeSt : schoolSt]} + + . + + )} + {selection === "toss-up" && ( + <> + Your vote counts the same in{" "} + {STATE_NAMES[homeSt]} and{" "} + {STATE_NAMES[schoolSt]}. + + )} +
+

Put some explanatory text here.

+
+
+ {browserSupportsShare() ? ( + + ) : ( + " " + )} +
+ +
+
+ ); }; export const More: React.FC = () => { diff --git a/src/components/ShareButton.tsx b/src/components/ShareButton.tsx index 07c6426..12bcfc9 100644 --- a/src/components/ShareButton.tsx +++ b/src/components/ShareButton.tsx @@ -1,5 +1,28 @@ import React, { useEffect, useState } from "react"; +/** Return `true` if this browser has native sharing panel support. */ +export const browserSupportsShare = () => + Boolean(window.navigator && window.navigator.share); + +/** Invoke a generic native share, if safe. */ +export const nativeShare = (title: string, text: string, url: string) => { + if (browserSupportsShare()) { + navigator.share({ + title, + text, + url, + }); + } +}; + +/** Share the default countmore information if possible. */ +export const defaultShare = () => + nativeShare( + "Count More", + "Every vote counts... but some count more. Find out where *your* vote counts more in 2024.", + "https://countmore.us" + ); + const ShareButton: React.FC = () => { const [showShare, setShowShare] = useState(false); diff --git a/src/vote_gov.ts b/src/vote_gov.ts index f57eb4b..799b3e4 100644 --- a/src/vote_gov.ts +++ b/src/vote_gov.ts @@ -182,6 +182,28 @@ export const VOTE_GOV_DATA: StateVotingInfo[] = [ url: "https://portaldir.ct.gov/sots/LookUp.aspx?ref=countmoreus_en", }, }, + { + state: "DC", + name: "District of Columbia", + scrapeUrl: "https://vote.gov/register/dc/", + hasRegistration: true, + online: { + url: "https://vr.dcboe.org/213324797239968?agency_code=12&?ref=countmoreus_en", + deadline: 21, + }, + mail: { + url: "https://www.dcboe.org/voters/register-to-vote/register-update-voter-registration?ref=countmoreus_en", + deadline: 21, + timeframe: "received", + }, + inPerson: { + url: "https://www.dcboe.org/voters/register-to-vote/register-update-voter-registration?ref=countmoreus_en", + deadline: 0, + }, + confirm: { + url: "https://vr.dcboe.org/213324797239968?agency_code=12&?ref=countmoreus_en", + }, + }, { state: "DE", name: "Delaware",