-
Notifications
You must be signed in to change notification settings - Fork 5
Add user profile subheader components + button group #668
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 10 commits
21165dd
5303b55
cb35474
bb4652d
68c8954
7bef8ed
c24b295
2f55fba
771de82
6b7c5b9
b5d90ef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 ( | ||
| <ButtonGroup> | ||
| <GroupedButton | ||
| aria-label="Remove from favorites" | ||
| size={size} | ||
| leftIcon={ | ||
| <StarSolidIcon className="w-[18px] fill fill-orange-800" /> | ||
| } | ||
| /> | ||
| <GroupedButton | ||
| size={size} | ||
| label="Compare favorites" | ||
| rightIcon={ | ||
| <ArrowFullRightIcon className="w-[13px] fill fill-orange-900/60" /> | ||
| } | ||
| /> | ||
| </ButtonGroup> | ||
| ); | ||
| }; | ||
|
|
||
| export default CompareFavoritesButton; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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" ? ( | ||
| <TransitIcon className={getIconStyle(size)} /> | ||
| ) : ( | ||
| <CarIcon className={getIconStyle(size)} /> | ||
| ); | ||
|
|
||
| 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<Key>([display])); | ||
|
|
||
| useEffect(() => { | ||
| setDisplayOption(new Set<Key>([display])); | ||
| }, [display]); | ||
|
|
||
| const onChangeDisplayOption = (keys: Set<Key>) => { | ||
| if (keys.size === 0) { | ||
| setDisplayOption(displayOption); | ||
| return; | ||
| } | ||
| setDisplayOption(keys); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="flex flex-column items-center gap-3"> | ||
| <Button | ||
| variant="outline" | ||
| size={size} | ||
| label="You" | ||
| leftIcon={<FamilyIcon className={getIconStyle(size)} />} | ||
| /> | ||
| <Button | ||
| variant="outline" | ||
| size={size} | ||
| label={place} | ||
| leftIcon={ | ||
| <div className="flex items-center gap-3"> | ||
| {getModeIcon(mode, size)} | ||
| <ArrowFullRightIcon className="font-normal fill-gray-400 w-[10px] -ml-1" /> | ||
| </div> | ||
| } | ||
| /> | ||
| <ToggleButtonGroup | ||
| selectionMode="single" | ||
| selectedKeys={displayOption} | ||
| onSelectionChange={onChangeDisplayOption} | ||
| > | ||
| <ToggleButton id="map" size={size}> | ||
| Map | ||
| </ToggleButton> | ||
| <ToggleButton id="list" size={size}> | ||
| List | ||
| </ToggleButton> | ||
| </ToggleButtonGroup> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export const UserProfileSubheader = ({ | ||
| mode, | ||
| place, | ||
| size = "medium", | ||
| }: Props) => { | ||
| return ( | ||
| <div className="flex flex-col gap-3 w-[360px]"> | ||
| <Button | ||
| variant="outline" | ||
| size={size} | ||
| label="Your Profile" | ||
| leftIcon={<FamilyIcon className={getIconStyle(size)} />} | ||
| info="2br・public transit" | ||
| /> | ||
| <Button | ||
| variant="outline" | ||
| size={size} | ||
| label={place} | ||
| leftIcon={getModeIcon(mode, size)} | ||
| info="122 Address St, Cambridge" | ||
| /> | ||
| </div> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<typeof buttonStyles> { | ||
| label?: string; | ||
| leftIcon?: React.ReactNode; | ||
| rightIcon?: React.ReactNode; | ||
| info?: string; | ||
| } | ||
|
|
||
| const Button = ({ | ||
| className, | ||
| variant, | ||
| size, | ||
| label, | ||
| leftIcon, | ||
| rightIcon, | ||
| info, | ||
| ...props | ||
| }: ButtonProps) => { | ||
| return ( | ||
| <AriaButton | ||
| {...props} | ||
| className={composeRenderProps(className, (className, renderProps) => | ||
| buttonStyles({ | ||
| ...renderProps, | ||
| variant, | ||
| size, | ||
| className, | ||
| }) | ||
| )} | ||
| > | ||
| {leftIcon} | ||
| {label && <span>{label}</span>} | ||
| {rightIcon} | ||
| {info && ( | ||
| <div className="flex flex-1 justify-end"> | ||
| <span className="text-sm font-normal text-gray-600"> | ||
| {info} | ||
| </span> | ||
| </div> | ||
| )} | ||
| </AriaButton> | ||
| ); | ||
| }; | ||
|
|
||
| export default Button; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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", | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There may be variants of this in other places. This orange one implements the styles for the "Compare favorites" button group |
||
| }, | ||
| size: { | ||
| ...buttonSizes, | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the button sizes are the same, so re-using the definitions from the styled buttons |
||
| }, | ||
| }, | ||
| defaultVariants: { | ||
| variant: "orange", | ||
| size: "medium", | ||
| }, | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<typeof groupedButtonStyles> { | ||
| label?: string; | ||
| leftIcon?: React.ReactNode; | ||
| rightIcon?: React.ReactNode; | ||
| } | ||
|
|
||
| const ButtonGroup = (props: AriaGroupProps) => ( | ||
| <AriaGroup | ||
| {...props} | ||
| className={composeButtonGroupRenderProps( | ||
| props.className, | ||
| (className, renderProps) => | ||
| buttonGroupStyles({ ...renderProps, className }) | ||
| )} | ||
| /> | ||
| ); | ||
|
|
||
| const GroupedButton = ({ | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is very similar to the styled Button component. There might be a chance to re-use the button component, and also parameterize |
||
| className, | ||
| variant, | ||
| size, | ||
| label, | ||
| leftIcon, | ||
| rightIcon, | ||
| ...props | ||
| }: GroupedButtonProps) => ( | ||
| <AriaButton | ||
| {...props} | ||
| className={composeButtonGroupRenderProps( | ||
| className, | ||
| (className, renderProps) => | ||
| groupedButtonStyles({ | ||
| ...renderProps, | ||
| variant, | ||
| size, | ||
| className, | ||
| }) | ||
| )} | ||
| > | ||
| {leftIcon} | ||
| {label && <span>{label}</span>} | ||
| {rightIcon} | ||
| </AriaButton> | ||
| ); | ||
|
|
||
| export { ButtonGroup, GroupedButton }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't a brand new component. It's moved over from
componentsintocomponents/basewith additional props like label, leftIcon, rightIcon, info, etc