diff --git a/CHANGELOG.md b/CHANGELOG.md index 31cf80d8..c1ffa5c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Bootstrap new frontend and remove old frontend [#666](https://github.com/azavea/echo-locator/pull/666) - Add Tailwind CSS theme [#667](https://github.com/azavea/echo-locator/pull/667) - Add a styled Button component [#667](https://github.com/azavea/echo-locator/pull/667) +- Add a styled Button Group component [#668](https://github.com/azavea/echo-locator/pull/668) +- Add user profile subheader components [#668](https://github.com/azavea/echo-locator/pull/668) ### Changed diff --git a/src/frontend/src/assets/icons/arrow-full-right.svg b/src/frontend/src/assets/icons/arrow-full-right.svg new file mode 100644 index 00000000..62841d10 --- /dev/null +++ b/src/frontend/src/assets/icons/arrow-full-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/frontend/src/assets/icons/car.svg b/src/frontend/src/assets/icons/car.svg new file mode 100644 index 00000000..262dbf84 --- /dev/null +++ b/src/frontend/src/assets/icons/car.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/frontend/src/assets/icons/family.svg b/src/frontend/src/assets/icons/family.svg new file mode 100644 index 00000000..09f6194a --- /dev/null +++ b/src/frontend/src/assets/icons/family.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/frontend/src/assets/icons/star-solid.svg b/src/frontend/src/assets/icons/star-solid.svg new file mode 100644 index 00000000..0d77b7f3 --- /dev/null +++ b/src/frontend/src/assets/icons/star-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/frontend/src/assets/icons/star.svg b/src/frontend/src/assets/icons/star.svg index a70d79a4..0190b2bc 100644 --- a/src/frontend/src/assets/icons/star.svg +++ b/src/frontend/src/assets/icons/star.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/src/frontend/src/assets/icons/transit.svg b/src/frontend/src/assets/icons/transit.svg new file mode 100644 index 00000000..35889707 --- /dev/null +++ b/src/frontend/src/assets/icons/transit.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/frontend/src/components/Button/Button.tsx b/src/frontend/src/components/Button/Button.tsx deleted file mode 100644 index b698d02d..00000000 --- a/src/frontend/src/components/Button/Button.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { - Button as AriaButton, - type ButtonProps as AriaButtonProps, - composeRenderProps, -} from "react-aria-components"; -import { type VariantProps } from "tailwind-variants"; - -import buttonStyles from "./Button.styles"; - -export interface ButtonProps - extends AriaButtonProps, - VariantProps {} - -const Button = ({ className, variant, size, ...props }: ButtonProps) => { - return ( - - buttonStyles({ - variant, - size, - }) - )} - /> - ); -}; - -export default Button; diff --git a/src/frontend/src/components/CompareFavoritesButton.tsx b/src/frontend/src/components/CompareFavoritesButton.tsx new file mode 100644 index 00000000..a746016d --- /dev/null +++ b/src/frontend/src/components/CompareFavoritesButton.tsx @@ -0,0 +1,33 @@ +import { + ButtonGroup, + GroupedButton, +} from "components/base/ButtonGroup/ButtonGroup"; + +import StarSolidIcon from "assets/icons/star-solid.svg?react"; +import ArrowFullRightIcon from "assets/icons/arrow-full-right.svg?react"; + +interface Props { + size?: "small" | "medium" | "large"; +} +const CompareFavoritesButton = ({ size = "medium" }: Props) => { + return ( + + + } + /> + + } + /> + + ); +}; + +export default CompareFavoritesButton; diff --git a/src/frontend/src/components/UserProfileSubheader.tsx b/src/frontend/src/components/UserProfileSubheader.tsx new file mode 100644 index 00000000..6bddbdb9 --- /dev/null +++ b/src/frontend/src/components/UserProfileSubheader.tsx @@ -0,0 +1,129 @@ +import type { Key } from "react-aria-components"; + +import Button from "components/base/Button/Button"; +import { + ToggleButton, + ToggleButtonGroup, +} from "components/base/ToggleButton/ToggleButton"; + +import FamilyIcon from "assets/icons/family.svg?react"; +import TransitIcon from "assets/icons/transit.svg?react"; +import CarIcon from "assets/icons/car.svg?react"; +import ArrowFullRightIcon from "assets/icons/arrow-full-right.svg?react"; +import { useEffect, useState } from "react"; + +export const Places = { + Work: "Work", + School: "School", + Daycare: "Daycare", + FriendsFamily: "Friends/Family", + Doctor: "Doctor", + Other: "Other", +}; + +type PlaceKeys = (typeof Places)[keyof typeof Places]; + +type Mode = "transit" | "car"; +type Size = "small" | "medium" | "large"; + +interface Props { + size?: Size; + mode: Mode; + place: PlaceKeys; +} + +interface MobileProps extends Props { + display: "map" | "list"; +} + +const getModeIcon = (mode: Mode, size: Size) => + mode === "transit" ? ( + + ) : ( + + ); + +const getIconStyle = (size: Size) => + size === "small" + ? "font-normal text-gray-500 w-[13px]" + : "font-normal text-gray-500 w-[17px]"; + +export const UserProfileSubheaderMobile = ({ + mode, + place, + size = "medium", + display = "map", +}: MobileProps) => { + const [displayOption, setDisplayOption] = useState(new Set([display])); + + useEffect(() => { + setDisplayOption(new Set([display])); + }, [display]); + + const onChangeDisplayOption = (keys: Set) => { + if (keys.size === 0) { + setDisplayOption(displayOption); + return; + } + setDisplayOption(keys); + }; + + return ( + + } + /> + + {getModeIcon(mode, size)} + + + } + /> + + + Map + + + List + + + + ); +}; + +export const UserProfileSubheader = ({ + mode, + place, + size = "medium", +}: Props) => { + return ( + + } + info="2br・public transit" + /> + + + ); +}; diff --git a/src/frontend/src/components/Button/Button.styles.ts b/src/frontend/src/components/base/Button/Button.styles.ts similarity index 77% rename from src/frontend/src/components/Button/Button.styles.ts rename to src/frontend/src/components/base/Button/Button.styles.ts index c0621b05..ae878c92 100644 --- a/src/frontend/src/components/Button/Button.styles.ts +++ b/src/frontend/src/components/base/Button/Button.styles.ts @@ -1,10 +1,16 @@ import { tv } from "tailwind-variants"; +export const buttonSizes = { + small: "h-[34px] px-4 py-2 text-sm gap-2", + medium: "h-8 px-4 py-2 text-sm", + large: "h-9 px-5 py-2 text-[17px]", // this should be text-rg, but somehow the text color will be all black +}; + const buttonStyles = tv({ base: [ // Base styles "inline-flex items-center justify-center whitespace-nowrap rounded-[var(--spacing-4)] gap-2.5 transition-colors", - "font-bold text-button leading-normal", + "font-bold text-button leading-normal capitalize", "cursor-pointer", // Focus, disabled, and pressed states from react-aria-components plugin "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-muted-blue-600 focus-visible:ring-offset-2", @@ -20,9 +26,7 @@ const buttonStyles = tv({ orange: "bg-orange-200 text-orange-800 hover:bg-orange-300", }, size: { - small: "h-[34px] px-4 py-2 text-sm gap-2", - medium: "h-8 px-4 py-2 text-sm", - large: "h-9 px-5 py-2 text-[17px]", // this should be text-rg, but somehow the text color will be all black + ...buttonSizes, icon: "h-[30px] w-[30px] text-[15px]", }, }, diff --git a/src/frontend/src/components/base/Button/Button.tsx b/src/frontend/src/components/base/Button/Button.tsx new file mode 100644 index 00000000..c801528c --- /dev/null +++ b/src/frontend/src/components/base/Button/Button.tsx @@ -0,0 +1,55 @@ +import { + Button as AriaButton, + type ButtonProps as AriaButtonProps, + composeRenderProps, +} from "react-aria-components"; +import { type VariantProps } from "tailwind-variants"; + +import buttonStyles from "./Button.styles"; + +export interface ButtonProps + extends AriaButtonProps, + VariantProps { + label?: string; + leftIcon?: React.ReactNode; + rightIcon?: React.ReactNode; + info?: string; +} + +const Button = ({ + className, + variant, + size, + label, + leftIcon, + rightIcon, + info, + ...props +}: ButtonProps) => { + return ( + + buttonStyles({ + ...renderProps, + variant, + size, + className, + }) + )} + > + {leftIcon} + {label && {label}} + {rightIcon} + {info && ( + + + {info} + + + )} + + ); +}; + +export default Button; diff --git a/src/frontend/src/components/base/ButtonGroup/ButtonGroup.styles.ts b/src/frontend/src/components/base/ButtonGroup/ButtonGroup.styles.ts new file mode 100644 index 00000000..8148adb6 --- /dev/null +++ b/src/frontend/src/components/base/ButtonGroup/ButtonGroup.styles.ts @@ -0,0 +1,30 @@ +import { tv } from "tailwind-variants"; +import { buttonSizes } from "components/base/Button/Button.styles"; + +export const buttonGroupStyles = tv({ + base: "inline-flex items-center overflow-hidden rounded-[var(--spacing-4)]", +}); + +export const groupedButtonStyles = tv({ + base: [ + "flex h-full items-center gap-4 px-4 py-2 transition-colors", + "font-bold text-button leading-normal capitalize", + "cursor-pointer", + "focus-visible:outline-none focus-visible:z-10 focus-visible:bg-orange-300", + "pressed:scale-[0.98]", + // Add a vertical separator + "not-first:border-l not-first:-ml-1", + ], + variants: { + variant: { + orange: "bg-orange-200 text-orange-800 hover:bg-orange-300 not-first:border-orange-800/25", + }, + size: { + ...buttonSizes, + }, + }, + defaultVariants: { + variant: "orange", + size: "medium", + }, +}); diff --git a/src/frontend/src/components/base/ButtonGroup/ButtonGroup.tsx b/src/frontend/src/components/base/ButtonGroup/ButtonGroup.tsx new file mode 100644 index 00000000..ef655b22 --- /dev/null +++ b/src/frontend/src/components/base/ButtonGroup/ButtonGroup.tsx @@ -0,0 +1,59 @@ +import { + Group as AriaGroup, + Button as AriaButton, + type GroupProps as AriaGroupProps, + type ButtonProps as AriaButtonProps, + composeRenderProps as composeButtonGroupRenderProps, +} from "react-aria-components"; +import { type VariantProps } from "tailwind-variants"; + +import { buttonGroupStyles, groupedButtonStyles } from "./ButtonGroup.styles"; + +export interface GroupedButtonProps + extends AriaButtonProps, + VariantProps { + label?: string; + leftIcon?: React.ReactNode; + rightIcon?: React.ReactNode; +} + +const ButtonGroup = (props: AriaGroupProps) => ( + + buttonGroupStyles({ ...renderProps, className }) + )} + /> +); + +const GroupedButton = ({ + className, + variant, + size, + label, + leftIcon, + rightIcon, + ...props +}: GroupedButtonProps) => ( + + groupedButtonStyles({ + ...renderProps, + variant, + size, + className, + }) + )} + > + {leftIcon} + {label && {label}} + {rightIcon} + +); + +export { ButtonGroup, GroupedButton }; diff --git a/src/frontend/src/components/base/ToggleButton/ToggleButton.styles.ts b/src/frontend/src/components/base/ToggleButton/ToggleButton.styles.ts new file mode 100644 index 00000000..56fa65d0 --- /dev/null +++ b/src/frontend/src/components/base/ToggleButton/ToggleButton.styles.ts @@ -0,0 +1,20 @@ +import { tv } from "tailwind-variants"; + +export const toggleGroupStyles = tv({ + base: "inline-flex items-center justify-center rounded-[var(--spacing-4)] border border-gray-300 bg-gray-100 overflow-hidden", +}); + +export const toggleButtonStyles = tv({ + base: "flex items-center justify-center text-md outline-0 outline-gray-300 font-semibold transition-colors cursor-pointer gap-2.5 rounded-[var(--spacing-4)] px-[10px] py-3 focus-visible:ring-2 ring-teal-700 focus-visible:ring-2 ring-inset ring-offset-2 focus-visible:ring-offset-2 focus-visible:ring-offset-white", + variants: { + isSelected: { + true: "bg-white text-gray-900 shadow-sm outline-1 rounded-[11px]", + false: "bg-transparent text-gray-600 hover:text-gray-900 border-none outline-0", + }, + size: { + small: "h-[34px] px-[10px] py-3 text-sm", + medium: "h-8 px-4 py-3 text-sm", + large: "h-9 px-4 py-5 text-[17px]", // this should be text-rg, but somehow the text color will be all black + }, + }, +}); diff --git a/src/frontend/src/components/base/ToggleButton/ToggleButton.tsx b/src/frontend/src/components/base/ToggleButton/ToggleButton.tsx new file mode 100644 index 00000000..50a34a7f --- /dev/null +++ b/src/frontend/src/components/base/ToggleButton/ToggleButton.tsx @@ -0,0 +1,44 @@ +import { + ToggleButton as AriaToggleButton, + ToggleButtonGroup as AriaToggleButtonGroup, + type ToggleButtonGroupProps as AriaToggleButtonGroupProps, + type ToggleButtonProps as AriaToggleButtonProps, + composeRenderProps, +} from "react-aria-components"; +import { toggleGroupStyles, toggleButtonStyles } from "./ToggleButton.styles"; +import { type VariantProps } from "tailwind-variants"; + +export interface ToggleButtonGroupProps + extends AriaToggleButtonGroupProps, + VariantProps {} + +export interface ToggleButtonProps + extends AriaToggleButtonProps, + VariantProps {} + +const ToggleButtonGroup = ({ className, ...props }: ToggleButtonGroupProps) => ( + + toggleGroupStyles({ + ...renderProps, + className, + }) + )} + /> +); + +const ToggleButton = ({ size, className, ...props }: ToggleButtonProps) => ( + + toggleButtonStyles({ + isSelected, + size, + className: className as string, + }) + } + /> +); + +export { ToggleButton, ToggleButtonGroup }; diff --git a/src/frontend/src/pages/Components.tsx b/src/frontend/src/pages/Components.tsx index 57e59ad3..41a3825c 100644 --- a/src/frontend/src/pages/Components.tsx +++ b/src/frontend/src/pages/Components.tsx @@ -1,7 +1,14 @@ -import Button from "components/Button/Button"; -import StarIcon from "assets/icons/star.svg?react"; +import Button from "components/base/Button/Button"; +import { + Places, + UserProfileSubheader, + UserProfileSubheaderMobile, +} from "components/UserProfileSubheader"; +import CompareFavoritesButton from "components/CompareFavoritesButton"; + import ArrowLeftIcon from "assets/icons/arrow-left.svg?react"; import ArrowRightIcon from "assets/icons/arrow-right.svg?react"; +import StarIcon from "assets/icons/star.svg?react"; const Components = () => { return ( @@ -29,29 +36,49 @@ const Components = () => { Small - - Primary - - - Secondary - - - Outline - - - - - - Disabled - - - Next - - - - - Add to favorites - + + + + + } + /> + + + } + /> + + } + /> @@ -59,21 +86,30 @@ const Components = () => { Medium - Primary - Secondary - Outline - - - - Disabled - - Next - - - - - Add to favorites - + + + + + } + /> + + + } + /> + + } + /> @@ -81,33 +117,143 @@ const Components = () => { Large - - Primary - - - Secondary - - - Outline - - - - - - Disabled - - - Next - - - - - Add to favorites - + + + + + } + /> + + + } + /> + + } + /> + + + + + + {/* Section for Button Group */} + + + Button group + + + + + Small + + + + + + + + Medium + + + + + + + + Large + + + + + {/* Section for User profile sub header */} + + + User profile subheader + + + + + Small + + + + + + + Medium + + + + + + + Large + + + + + + );