Skip to content
Merged
1 change: 1 addition & 0 deletions src/frontend/src/assets/icons/arrow-full-right.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/frontend/src/assets/icons/car.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/frontend/src/assets/icons/family.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/frontend/src/assets/icons/star-solid.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 1 addition & 3 deletions src/frontend/src/assets/icons/star.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/frontend/src/assets/icons/transit.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 0 additions & 31 deletions src/frontend/src/components/Button/Button.tsx

This file was deleted.

33 changes: 33 additions & 0 deletions src/frontend/src/components/CompareFavoritesButton.tsx
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;
129 changes: 129 additions & 0 deletions src/frontend/src/components/UserProfileSubheader.tsx
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
@@ -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",
Expand All @@ -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]",
},
},
Expand Down
55 changes: 55 additions & 0 deletions src/frontend/src/components/base/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {

Copy link
Copy Markdown
Member Author

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 components into components/base with additional props like label, leftIcon, rightIcon, info, etc

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;
30 changes: 30 additions & 0 deletions src/frontend/src/components/base/ButtonGroup/ButtonGroup.styles.ts
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",

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The 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,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The 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",
},
});
59 changes: 59 additions & 0 deletions src/frontend/src/components/base/ButtonGroup/ButtonGroup.tsx
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 = ({

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The 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 groupedButtonStyles and buttonStyles as a function input?

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 };
Loading