Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@
"@tanstack/react-query": "^4.28.0",
"classnames": "^2.3.2",
"luxon": "^3.3.0",
"seamapi": "^8.10.1",
"seamapi": "^8.11.0",
"uuid": "^9.0.0"
},
"devDependencies": {
Expand Down
32 changes: 32 additions & 0 deletions src/lib/ui/thermostat/ClimateModeMenu.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Box } from '@mui/material'
import { useArgs } from '@storybook/preview-api'
import type { Meta, StoryObj } from '@storybook/react'

import { ClimateModeMenu } from './ClimateModeMenu.js'

const meta: Meta<typeof ClimateModeMenu> = {
title: 'Library/ClimateModeMenu',
tags: ['autodocs'],
component: ClimateModeMenu,
}

type Story = StoryObj<typeof ClimateModeMenu>

export const Content: Story = {
render: (props) => {
const [, setArgs] = useArgs()

return (
<Box height={190}>
<ClimateModeMenu
mode={props.mode ?? 'heat'}
onChange={(mode) => {
setArgs({ mode })
}}
/>
</Box>
)
},
}

export default meta
71 changes: 71 additions & 0 deletions src/lib/ui/thermostat/ClimateModeMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import type { HvacModeSetting } from 'seamapi'

import { ChevronDownIcon } from 'lib/icons/ChevronDown.js'
import { OffIcon } from 'lib/icons/Off.js'
import { ThermostatCoolIcon } from 'lib/icons/ThermostatCool.js'
import { ThermostatHeatIcon } from 'lib/icons/ThermostatHeat.js'
import { ThermostatHeatCoolIcon } from 'lib/icons/ThermostatHeatCool.js'
import { Menu } from 'lib/ui/Menu/Menu.js'
import { ThermoModeMenuOption } from 'lib/ui/thermostat/ThermoModeMenuOption.js'

const modes: HvacModeSetting[] = ['heat', 'cool', 'heat_cool', 'off']

interface ClimateModeMenuProps {
mode: HvacModeSetting
onChange: (mode: HvacModeSetting) => void
}

export function ClimateModeMenu({
mode,
onChange,
}: ClimateModeMenuProps): JSX.Element {
return (
<Menu
renderButton={({ onOpen }) => (
<button onClick={onOpen} className='seam-climate-mode-menu-button'>
<div className='seam-climate-mode-menu-button-icon'>
{ModeIcon(mode)}
</div>
<ChevronDownIcon />
</button>
)}
verticalOffset={-180}
horizontalOffset={-32}
backgroundProps={{
className: 'seam-thermo-mode-menu',
}}
>
{modes.map((m) => (
<ThermoModeMenuOption
key={m}
label={t[m]}
icon={ModeIcon(m)}
isSelected={mode === m}
onClick={() => {
onChange(m)
}}
/>
))}
</Menu>
)
}

function ModeIcon(mode: HvacModeSetting): JSX.Element {
switch (mode) {
case 'heat':
return <ThermostatHeatIcon />
case 'cool':
return <ThermostatCoolIcon />
case 'heat_cool':
return <ThermostatHeatCoolIcon />
case 'off':
return <OffIcon />
}
}

const t = {
heat: 'Heat',
cool: 'Cool',
heat_cool: 'Heat & Cool',
off: 'Off',
}
60 changes: 18 additions & 42 deletions src/lib/ui/thermostat/FanModeMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { CheckBlackIcon } from 'lib/icons/CheckBlack.js'
import type { FanModeSetting } from 'seamapi'

import { ChevronDownIcon } from 'lib/icons/ChevronDown.js'
import { FanIcon } from 'lib/icons/Fan.js'
import { FanOutlineIcon } from 'lib/icons/FanOutline.js'
import { Menu } from 'lib/ui/Menu/Menu.js'
import { MenuItem } from 'lib/ui/Menu/MenuItem.js'
import { ThermoModeMenuOption } from 'lib/ui/thermostat/ThermoModeMenuOption.js'

type Mode = 'auto' | 'on'
const modes: FanModeSetting[] = ['auto', 'on']

interface FanModeMenuProps {
mode: Mode
onChange: (mode: Mode) => void
mode: FanModeSetting
onChange: (mode: FanModeSetting) => void
}

export function FanModeMenu({ mode, onChange }: FanModeMenuProps): JSX.Element {
Expand All @@ -29,49 +30,24 @@ export function FanModeMenu({ mode, onChange }: FanModeMenuProps): JSX.Element {
verticalOffset={-180}
horizontalOffset={-32}
backgroundProps={{
className: 'seam-fan-mode-menu-bg',
className: 'seam-thermo-mode-menu',
}}
>
<Option
mode='auto'
isSelected={mode === 'auto'}
onClick={() => {
onChange('auto')
}}
/>
<Option
mode='on'
isSelected={mode === 'on'}
onClick={() => {
onChange('on')
}}
/>
{modes.map((m) => (
<ThermoModeMenuOption
key={m}
label={t[m]}
icon={<FanIcon />}
isSelected={mode === m}
onClick={() => {
onChange(m)
}}
/>
))}
</Menu>
)
}

interface OptionProps {
mode: Mode
onClick: () => void
isSelected: boolean
}

function Option({ mode, isSelected, onClick }: OptionProps): JSX.Element {
return (
<MenuItem onClick={onClick}>
<div className='seam-fan-mode-menu-item'>
<div className='seam-fan-mode-menu-item-block'>
<FanIcon />
<span>{mode === 'auto' ? t.auto : t.on}</span>
</div>
<div className='seam-fan-mode-menu-item-block'>
{isSelected && <CheckBlackIcon />}
</div>
</div>
</MenuItem>
)
}

const t = {
auto: 'Auto',
on: 'On',
Expand Down
30 changes: 30 additions & 0 deletions src/lib/ui/thermostat/ThermoModeMenuOption.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { CheckBlackIcon } from 'lib/icons/CheckBlack.js'
import { MenuItem } from 'lib/ui/Menu/MenuItem.js'

interface ThermoModeMenuOptionProps {
label: string
icon: JSX.Element
onClick: () => void
isSelected: boolean
}

export function ThermoModeMenuOption({
label,
icon,
isSelected,
onClick,
}: ThermoModeMenuOptionProps): JSX.Element {
return (
<MenuItem onClick={onClick}>
<div className='seam-thermo-mode-menu-item'>
<div className='seam-thermo-mode-menu-item-block'>
<div className='seam-thermo-mode-menu-icon'>{icon}</div>
<span>{label}</span>
</div>
<div className='seam-thermo-mode-menu-item-block'>
{isSelected && <CheckBlackIcon />}
</div>
</div>
</MenuItem>
)
}
64 changes: 57 additions & 7 deletions src/styles/_thermostat.scss
Original file line number Diff line number Diff line change
Expand Up @@ -559,16 +559,42 @@
}
}

@mixin fan-mode-menu {
.seam-fan-mode-menu-bg {
@mixin mode-menu-common {
.seam-thermo-mode-menu {
.seam-menu-content {
max-width: 180px;
box-shadow:
0 2px 16px 2px rgb(15 22 28 / 15%),
0 0 1px 0 rgb(0 0 0 / 30%);
}

.seam-thermo-mode-menu-item {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: row;
}

.seam-thermo-mode-menu-item-block {
display: flex;
justify-content: center;
align-items: center;
flex-direction: row;
gap: 12px;
}
}

.seam-thermo-mode-menu-icon {
width: 18px;
height: 18px;
display: flex;
justify-content: center;
align-items: center;
}
}

@mixin fan-mode-menu {
.seam-fan-mode-menu-button {
width: 130px;
height: 32px;
Expand Down Expand Up @@ -607,27 +633,51 @@
line-height: 120%;
}
}
}

.seam-fan-mode-menu-item {
width: 100%;
@mixin climate-mode-menu {
.seam-climate-mode-menu-button {
width: 72px;
height: 32px;
padding: 4px 8px;
border-radius: 6px;
border: 1px solid colors.$text-gray-3;
background: colors.$white;
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: row;
cursor: pointer;
transition: 0.2s ease;

&:hover {
border-color: colors.$text-gray-2;
}

&:focus {
outline-color: colors.$text-gray-2;
}

&:active {
background-color: colors.$bg-c;
border-color: colors.$text-gray-2;
}
}

.seam-fan-mode-menu-item-block {
.seam-climate-mode-menu-button-icon {
width: 22px;
height: 22px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: row;
gap: 12px;
}
}

@mixin all {
@include temperature-control;
@include climate-setting-status;
@include thermostat-card;
@include mode-menu-common;
@include fan-mode-menu;
@include climate-mode-menu;
}