diff --git a/package.json b/package.json index 286b330..1b7afbd 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "clsx": "^2.1.0", "crypto-js": "^4.2.0", "jest-environment-jsdom": "^29.7.0", + "lodash": "^4.17.21", "lucide-react": "^0.352.0", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -35,6 +36,7 @@ "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@types/crypto-js": "^4.2.2", "@types/jest": "^29.5.11", + "@types/lodash": "^4.17.0", "@types/node": "^20.11.24", "@types/react": "^18.2.43", "@types/react-dom": "^18.2.17", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index da26c0c..02253fb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ dependencies: jest-environment-jsdom: specifier: ^29.7.0 version: 29.7.0 + lodash: + specifier: ^4.17.21 + version: 4.17.21 lucide-react: specifier: ^0.352.0 version: 0.352.0(react@18.2.0) @@ -70,6 +73,9 @@ devDependencies: '@types/jest': specifier: ^29.5.11 version: 29.5.12 + '@types/lodash': + specifier: ^4.17.0 + version: 4.17.0 '@types/node': specifier: ^20.11.24 version: 20.11.24 @@ -2056,6 +2062,10 @@ packages: resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} dev: true + /@types/lodash@4.17.0: + resolution: {integrity: sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==} + dev: true + /@types/node@20.11.24: resolution: {integrity: sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==} dependencies: @@ -4818,7 +4828,6 @@ packages: /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - dev: true /loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} diff --git a/src/components/AddDeviceFooter/AddDeviceFooter.tsx b/src/components/AddDeviceFooter/AddDeviceFooter.tsx index 8a77307..bff2474 100644 --- a/src/components/AddDeviceFooter/AddDeviceFooter.tsx +++ b/src/components/AddDeviceFooter/AddDeviceFooter.tsx @@ -1,6 +1,5 @@ import { useDevicesLocalStorage } from '@/hooks'; import { Pax } from '@/pax'; -import { useState } from 'react'; import { SUPPORTED_DEVICES } from '../DevicesModal/constants'; import { Button } from '../ui/button'; @@ -13,6 +12,10 @@ const INPUT_PLACEHOLDER = `Insert ${SERIAL_SIZE} digits serial`; interface Props { onFocus?: React.FocusEventHandler; onBlur?: React.FocusEventHandler; + serialInput?: string; + setSerialInput: React.Dispatch>; + deviceValue: Pax.lib.Devices; + setDeviceValue: React.Dispatch>; } const buildOptions = (devices: Pax.lib.Devices[]) => { @@ -29,10 +32,7 @@ const addNewDeviceButtonDisabled = (serialInput: string | undefined) => { }; const AddDeviceFooter = (props: Props) => { - const defaultDevice = SUPPORTED_DEVICES[0]; - const [serialInput, setSerialInput] = useState(undefined); - const [deviceValue, setDeviceValue] = - useState(defaultDevice); + const { serialInput, setSerialInput, deviceValue, setDeviceValue } = props; const deviceStore = useDevicesLocalStorage(); diff --git a/src/components/DevicesModal/DevicesModal.tsx b/src/components/DevicesModal/DevicesModal.tsx index 31d6c0a..53b4d76 100644 --- a/src/components/DevicesModal/DevicesModal.tsx +++ b/src/components/DevicesModal/DevicesModal.tsx @@ -1,9 +1,12 @@ import { Modal } from '@/components'; import { useDevicesLocalStorage } from '@/hooks'; +import { Pax } from '@/pax'; import { TriangleAlert } from 'lucide-react'; +import { useState } from 'react'; import AddDeviceFooter from '../AddDeviceFooter'; import DeviceCard from './DeviceCard'; +import { SUPPORTED_DEVICES } from './constants'; export interface DevicesModalProps { open: boolean; @@ -11,6 +14,10 @@ export interface DevicesModalProps { } const DevicesModal = ({ open, onOpenChange }: DevicesModalProps) => { + const [serialInput, setSerialInput] = useState(undefined); + const defaultDevice = SUPPORTED_DEVICES[0]; + const [deviceValue, setDeviceValue] = + useState(defaultDevice); const deviceStore = useDevicesLocalStorage(); const renderCards = () => { @@ -34,7 +41,14 @@ const DevicesModal = ({ open, onOpenChange }: DevicesModalProps) => { }; const renderFooter = () => { - return ; + return ( + + ); }; return ( diff --git a/src/components/Graphics/PaxPairingSvg/PairingLight.tsx b/src/components/Graphics/PaxPairingSvg/PairingLight.tsx new file mode 100644 index 0000000..e210319 --- /dev/null +++ b/src/components/Graphics/PaxPairingSvg/PairingLight.tsx @@ -0,0 +1,71 @@ +import React, { useEffect, useState } from 'react'; + +interface PairingLightProps { + radius?: string; + color: string; + speed?: 'slow' | 'normal' | 'fast'; +} + +const PairingLight = (props: PairingLightProps) => { + const { speed = 'normal', color, radius } = props; + const [isBloomed, setIsBloomed] = useState(false); + const getSpeed = (speed: 'fast' | 'normal' | 'slow') => { + switch (speed) { + case 'fast': + return 0.5; + case 'slow': + return 1.5; + default: + return 1; + } + }; + const speedInSeconds = getSpeed(speed); + + useEffect(() => { + const interval = setInterval(() => { + setIsBloomed(prevState => !prevState); + }, speedInSeconds * 1000); + return () => clearInterval(interval); + }, [speedInSeconds]); + + const animationStyle: React.CSSProperties = { + width: radius ? radius : '100px', + height: radius ? radius : '100px', + borderRadius: '50%', + backgroundColor: color, + boxShadow: isBloomed ? `0 0 50px 10px ${color}` : 'none', + transition: `box-shadow ${speedInSeconds}s ease-in-out`, + animation: 'blink 1s step-end infinite', + }; + + const buildCircle = (x: string, y: string) => { + return ( +
+ ); + }; + + return ( +
+ {buildCircle('60%', '50%')} + {buildCircle('40%', '50%')} + {buildCircle('50%', '60%')} + {buildCircle('50%', '40%')} +
+ ); +}; + +export default PairingLight; diff --git a/src/components/Graphics/PaxPairingSvg/PaxAddSerial.tsx b/src/components/Graphics/PaxPairingSvg/PaxAddSerial.tsx new file mode 100644 index 0000000..5b0615d --- /dev/null +++ b/src/components/Graphics/PaxPairingSvg/PaxAddSerial.tsx @@ -0,0 +1,45 @@ +import ParallaxComponent from './ParallaxComponent'; +import Pax3DeviceSvg from './Svg/Pax3DeviceSvg'; +import './shakingAnimation.css'; + +interface Props { + parallax?: boolean; + serial?: { serial?: string; device: string }; +} + +const PaxAddSerial = (props: Props) => { + const svgFillColor = '#666666'; + + const renderWithParallax = ( + multiplier: number, + children: React.ReactNode, + parallax?: boolean, + ) => { + if (!parallax) { + return children; + } + + return ( + {children} + ); + }; + + return renderWithParallax( + 7, +
+
+ +
+

+ {props.serial?.device} +

+
+
+

{props.serial?.serial}

+
+
, + props.parallax, + ); +}; + +export default PaxAddSerial; diff --git a/src/components/Graphics/PaxPairingSvg/PaxPairing.tsx b/src/components/Graphics/PaxPairingSvg/PaxPairing.tsx index 6b721a9..6beb653 100644 --- a/src/components/Graphics/PaxPairingSvg/PaxPairing.tsx +++ b/src/components/Graphics/PaxPairingSvg/PaxPairing.tsx @@ -1,13 +1,16 @@ import ParallaxComponent from './ParallaxComponent'; import PulsatingLight from './PulsatingLight'; import Pax3DeviceSvg from './Svg/Pax3DeviceSvg'; +import './shakingAnimation.css'; interface Props { parallax?: boolean; pulsatingLightSpeed?: 'slow' | 'normal' | 'fast'; + pairingAnimation?: boolean; } const PaxPairing = (props: Props) => { + const { pairingAnimation = false } = props; const svgFillColor = '#666666'; const lightColor = '#00FFFF'; @@ -26,7 +29,17 @@ const PaxPairing = (props: Props) => { }; return ( -
+
{renderWithParallax( 7, diff --git a/src/components/Graphics/PaxPairingSvg/index.ts b/src/components/Graphics/PaxPairingSvg/index.ts index d051259..85aa219 100644 --- a/src/components/Graphics/PaxPairingSvg/index.ts +++ b/src/components/Graphics/PaxPairingSvg/index.ts @@ -1 +1,2 @@ export { default } from './PaxPairing'; +export * from './PaxAddSerial'; diff --git a/src/components/Graphics/PaxPairingSvg/shakingAnimation.css b/src/components/Graphics/PaxPairingSvg/shakingAnimation.css new file mode 100644 index 0000000..2adc7df --- /dev/null +++ b/src/components/Graphics/PaxPairingSvg/shakingAnimation.css @@ -0,0 +1,5 @@ +@keyframes shake { + 0%, 100% { transform: rotate(0deg); } + 20%, 80% { transform: rotate(-10deg); } + 40%, 60% { transform: rotate(10deg); } + } diff --git a/src/components/Graphics/index.ts b/src/components/Graphics/index.ts index 2c839be..6c9c1d4 100644 --- a/src/components/Graphics/index.ts +++ b/src/components/Graphics/index.ts @@ -1 +1,2 @@ export { default as PaxPairing } from './PaxPairingSvg'; +export { default as PaxAddSerial } from './PaxPairingSvg/PaxAddSerial'; diff --git a/src/components/MainContent/MainContent.tsx b/src/components/MainContent/MainContent.tsx index f9ece6b..0ae1c0b 100644 --- a/src/components/MainContent/MainContent.tsx +++ b/src/components/MainContent/MainContent.tsx @@ -15,7 +15,9 @@ const MainContent = () => { currentDevice: Pax.lib.PaxSerial | undefined, ) => { return !currentDevice ? ( - +
+ +
) : ( { const isMobile = useIsMobile(); - const [isInputOnFocus, setIsInputOnFocus] = useState(false); + const [serialInput, setSerialInput] = useState(undefined); + const defaultDevice = SUPPORTED_DEVICES[0]; + const [deviceValue, setDeviceValue] = + useState(defaultDevice); return ( -
- + setIsInputOnFocus(true)} - onBlur={() => setIsInputOnFocus(false)} + serialInput={serialInput} + setSerialInput={setSerialInput} + deviceValue={deviceValue} + setDeviceValue={setDeviceValue} /> + + + Heads up! + + Make sure to add the correct serial from the back of your device. + +
); }; diff --git a/src/components/MainContent/SelectedDevice.tsx b/src/components/MainContent/SelectedDevice.tsx index 77555ea..f94d694 100644 --- a/src/components/MainContent/SelectedDevice.tsx +++ b/src/components/MainContent/SelectedDevice.tsx @@ -1,22 +1,26 @@ import { usePaxBluetoothServices } from '@/hooks'; import { BaseBluetoothException } from '@/hooks/usePaxBluetoothServices/useBluetooth/exceptions'; import { Pax } from '@/pax'; +import { post } from '@/pax/containers/api'; +import { ColorThemeMessage } from '@/pax/core/messages'; +import { ColorTheme } from '@/pax/shared/types'; import { usePaxContext } from '@/state/hooks'; +import { isEqual } from 'lodash'; import { useCallback, useEffect } from 'react'; import HeaterStatus from '../HeaterStatus'; import TemperatureProgress from '../TemperatureProgress'; +import { ThemePicker } from '../ThemePicker'; +import { hardcodedThemes } from '../ThemePicker/colors'; import { Button } from '../ui/button'; +import { Connect } from './SelectedDevice/Connect'; interface SelectedDeviceProps { currentDevice: Pax.lib.PaxSerial; openDevicesModal: () => void; } -export const SelectedDevice = ({ - currentDevice, - openDevicesModal, -}: SelectedDeviceProps) => { +export const SelectedDevice = ({ currentDevice }: SelectedDeviceProps) => { const { state, actions } = usePaxContext(); const bluetoothState = usePaxBluetoothServices(currentDevice); @@ -40,6 +44,9 @@ export const SelectedDevice = ({ if (message instanceof Pax.lib.messages.HeatingStateMessage) { actions.setHeatingState(message.heatingState); } + if (message instanceof Pax.lib.messages.ColorThemeMessage) { + actions.setColorTheme(message.theme); + } }) .catch(e => { if (e instanceof BaseBluetoothException) { @@ -67,6 +74,14 @@ export const SelectedDevice = ({ } }, [actions, bluetoothState.connected]); + if (!bluetoothState.connected) { + return ( +
+ +
+ ); + } + return (
-

Current Device: {!currentDevice ? '' : currentDevice.serial}

- +

Device: {!currentDevice ? '' : currentDevice.serial}

+ + isEqual(theme.theme, state.colorTheme), + )} + onClick={(selectedTheme: ColorTheme) => { + const toPost = post( + ColorThemeMessage.createWithTheme(selectedTheme), + currentDevice, + ); + bluetoothState + .writeToMainService(toPost.packet) + .then(() => { + actions.setColorTheme(selectedTheme); + }) + .catch(e => { + if (e instanceof BaseBluetoothException) { + // eslint-disable-next-line no-console + console.error('ThemePicker', String(e)); + } + }); + }} + />
); }; diff --git a/src/components/MainContent/SelectedDevice/Connect.tsx b/src/components/MainContent/SelectedDevice/Connect.tsx new file mode 100644 index 0000000..ceff68c --- /dev/null +++ b/src/components/MainContent/SelectedDevice/Connect.tsx @@ -0,0 +1,34 @@ +import { PaxPairing } from '@/components/Graphics'; +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; +import { Button } from '@/components/ui/button'; +import { useIsMobile } from '@/hooks'; +import { Terminal } from 'lucide-react'; + +export interface IConnectProps { + connect: () => Promise; +} + +export function Connect(props: IConnectProps) { + const isMobile = useIsMobile(); + + return ( +
+ + + + + + Pairing mode + + Shake your device ultil you see the blue light. + + +
+ ); +} diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 1f1690d..1da2777 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -1,22 +1,42 @@ +import { useDevicesLocalStorage } from '@/hooks'; import { Cloudy } from 'lucide-react'; +import { useState } from 'react'; import { ThemeDropdownButton } from '.'; +import DevicesModal from './DevicesModal'; +import { Button } from './ui/button'; const Navbar = () => { + const [isDeviceModalOpen, openDevicesModal] = useState(false); + const deviceStore = useDevicesLocalStorage(); + return ( -
-
- -

- Pax Romana -

+ <> + void} + /> +
+
+ +

+ Pax Romana +

+
+
+
+ + +
-
- -
+ ); }; diff --git a/src/components/TemperatureProgress.tsx b/src/components/TemperatureProgress.tsx index 30a1b7f..11c7378 100644 --- a/src/components/TemperatureProgress.tsx +++ b/src/components/TemperatureProgress.tsx @@ -18,7 +18,11 @@ const TemperatureProgress = ({ maxTemperature = 215, }: TemperatureProgressProps) => { const buildDefaultProgress = () => { - return ; + return ( +
+ +
+ ); }; const m = (1 - 0.01) / (maxTemperature - minTemperature); diff --git a/src/components/ThemePicker/ThemeCircle.tsx b/src/components/ThemePicker/ThemeCircle.tsx new file mode 100644 index 0000000..ba4af44 --- /dev/null +++ b/src/components/ThemePicker/ThemeCircle.tsx @@ -0,0 +1,39 @@ +import { cn } from '@/lib/utils'; + +export interface IThemeCircleProps { + size?: 'xm' | 'sm' | 'md' | 'lg' | 'xl'; + selected?: boolean; + className?: string; + onClick?: () => void; + style?: React.CSSProperties; +} + +export function ThemeCircle(props: IThemeCircleProps) { + const { size = 'md' } = props; + + const sizeStyle: Record = { + sm: 'h-10 w-10', + md: 'h-12 w-12', + lg: 'h-14 w-14', + xl: 'h-16 w-16', + }; + const selectedSizeStyle: Record = { + sm: 'h-10 w-10 border-2', + md: 'h-12 w-12 border-2', + lg: 'h-14 w-14 border-4', + xl: 'h-16 w-16 border-4', + }; + const baseStyle = `hover:cursor-pointer rounded-full dark:border-neutral-300 border-neutral-800`; + // const selected = props.selected ? + return ( +
+ ); +} diff --git a/src/components/ThemePicker/ThemePicker.tsx b/src/components/ThemePicker/ThemePicker.tsx new file mode 100644 index 0000000..2cdc080 --- /dev/null +++ b/src/components/ThemePicker/ThemePicker.tsx @@ -0,0 +1,61 @@ +import { ColorTheme } from '@/pax/shared/types/colorTheme'; + +import { ThemeCircle } from './ThemeCircle'; +import { PaxLightTheme } from './colors'; + +export interface IThemePickerProps { + colorThemes?: PaxLightTheme[]; + selectedThemeIndex?: number; + onClick?: (selectedTheme: ColorTheme) => void; + loading?: boolean; +} + +export function ThemePicker(props: IThemePickerProps) { + if (props.loading ?? !props.colorThemes) { + return ( +
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ ); + } + + return ( +
+ {props.colorThemes.map((theme, index) => { + return ( +
+ props.onClick?.(theme.theme)} + /> +

{theme.name}

+
+ ); + })} +
+ ); +} diff --git a/src/components/ThemePicker/colors.ts b/src/components/ThemePicker/colors.ts new file mode 100644 index 0000000..4d1cc4c --- /dev/null +++ b/src/components/ThemePicker/colors.ts @@ -0,0 +1,261 @@ +import { ColorTheme } from '@/pax/shared/types/colorTheme'; + +export interface PaxLightTheme { + name: string; + theme: ColorTheme; +} + +const themeDefault: PaxLightTheme = { + name: 'Default', + theme: { + heating: { + animation: 0, + color1: { + blue: 0, + green: 0, + red: 0, + }, + color2: { + blue: 0, + green: 0, + red: 0, + }, + frequency: 0, + }, + regulating: { + animation: 0, + color1: { + blue: 0, + green: 0, + red: 0, + }, + color2: { + blue: 0, + green: 0, + red: 0, + }, + frequency: 0, + }, + standby: { + animation: 0, + color1: { + blue: 0, + green: 0, + red: 0, + }, + color2: { + blue: 0, + green: 0, + red: 0, + }, + frequency: 0, + }, + startup: { + animation: 0, + color1: { + blue: 0, + green: 0, + red: 0, + }, + color2: { + blue: 0, + green: 0, + red: 0, + }, + frequency: 0, + }, + }, +}; + +const themeOcean: PaxLightTheme = { + name: 'Ocean', + theme: { + heating: { + animation: 0, + color1: { + blue: 40, + green: 7, + red: 19, + }, + color2: { + blue: 255, + green: 0, + red: 40, + }, + frequency: 16, + }, + regulating: { + animation: 2, + color1: { + blue: 67, + green: 92, + red: 36, + }, + color2: { + blue: 255, + green: 0, + red: 0, + }, + frequency: 9, + }, + standby: { + animation: 1, + color1: { + blue: 20, + green: 20, + red: 0, + }, + color2: { + blue: 255, + green: 0, + red: 0, + }, + frequency: 6, + }, + startup: { + animation: 0, + color1: { + blue: 255, + green: 0, + red: 0, + }, + color2: { + blue: 117, + green: 108, + red: 0, + }, + frequency: 17, + }, + }, +}; + +const themeSunset: PaxLightTheme = { + name: 'Sunset', + theme: { + heating: { + animation: 0, + color1: { + blue: 40, + green: 115, + red: 255, + }, + color2: { + blue: 104, + green: 0, + red: 153, + }, + frequency: 15, + }, + regulating: { + animation: 2, + color1: { + blue: 40, + green: 149, + red: 253, + }, + color2: { + blue: 136, + green: 0, + red: 153, + }, + frequency: 11, + }, + standby: { + animation: 1, + color1: { + blue: 125, + green: 114, + red: 255, + }, + color2: { + blue: 154, + green: 64, + red: 0, + }, + frequency: 6, + }, + startup: { + animation: 1, + color1: { + blue: 40, + green: 115, + red: 255, + }, + color2: { + blue: 104, + green: 0, + red: 153, + }, + frequency: 5, + }, + }, +}; + +const themeMars: PaxLightTheme = { + name: 'Mars', + theme: { + heating: { + animation: 0, + color1: { + blue: 0, + green: 0, + red: 255, + }, + color2: { + blue: 1, + green: 2, + red: 3, + }, + frequency: 17, + }, + regulating: { + animation: 2, + color1: { + blue: 3, + green: 22, + red: 255, + }, + color2: { + blue: 1, + green: 2, + red: 3, + }, + frequency: 9, + }, + standby: { + animation: 1, + color1: { + blue: 10, + green: 30, + red: 250, + }, + color2: { + blue: 12, + green: 15, + red: 4, + }, + frequency: 7, + }, + startup: { + animation: 2, + color1: { + blue: 0, + green: 0, + red: 250, + }, + color2: { + blue: 0, + green: 20, + red: 60, + }, + frequency: 27, + }, + }, +}; + +export const hardcodedThemes = [ + themeDefault, + themeOcean, + themeSunset, + themeMars, +]; diff --git a/src/components/ThemePicker/index.ts b/src/components/ThemePicker/index.ts new file mode 100644 index 0000000..746a9a2 --- /dev/null +++ b/src/components/ThemePicker/index.ts @@ -0,0 +1 @@ +export * from './ThemePicker'; diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx new file mode 100644 index 0000000..650306a --- /dev/null +++ b/src/components/ui/alert.tsx @@ -0,0 +1,63 @@ +import { cn } from '@/lib/utils'; +import { type VariantProps, cva } from 'class-variance-authority'; +import * as React from 'react'; + +const alertVariants = cva( + `relative w-full rounded-lg border border-neutral-200 p-4 [&>svg~*]:pl-7 + [&>svg+div]:tranneutral-y-[-3px] [&>svg]:absolute [&>svg]:left-4 + [&>svg]:top-4 [&>svg]:text-neutral-950 dark:border-neutral-800 + dark:[&>svg]:text-neutral-50`, + { + variants: { + variant: { + default: + 'bg-white text-neutral-950 dark:bg-neutral-950 dark:text-neutral-50', + destructive: `border-red-500/50 text-red-500 dark:border-red-500 [&>svg]:text-red-500 + dark:border-red-900/50 dark:text-red-900 dark:dark:border-red-900 + dark:[&>svg]:text-red-900`, + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +); + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)); +Alert.displayName = 'Alert'; + +const AlertTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertTitle.displayName = 'AlertTitle'; + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertDescription.displayName = 'AlertDescription'; + +export { Alert, AlertTitle, AlertDescription }; diff --git a/src/components/ui/progress-bar.tsx b/src/components/ui/progress-bar.tsx index d5fe280..7d3f8ae 100644 --- a/src/components/ui/progress-bar.tsx +++ b/src/components/ui/progress-bar.tsx @@ -16,7 +16,7 @@ const progressVariants = cva('fill-transparent', { interface CircularProgressBarProps extends VariantProps { percentage: number; - label: string; + label?: string; classNames?: { background?: ClassValue; foreground?: ClassValue; @@ -71,7 +71,7 @@ const CircularProgressBar = ({ /> )} - {label && ( + {label ? (
- {`${label}`} + {`${label}`}
+ ) : ( +
)}
); diff --git a/src/pax/containers/lib/index.ts b/src/pax/containers/lib/index.ts index c1999bf..63649c6 100644 --- a/src/pax/containers/lib/index.ts +++ b/src/pax/containers/lib/index.ts @@ -2,5 +2,5 @@ export * as messages from '../../core/messages'; export * from '../../shared/models/PaxSerial'; export * from '../../shared/enums/Devices'; export * from '../../shared/models/Packet'; -export * from '../../shared/types/hex'; +export * from '../../shared/types'; export * from '../../shared/enums/HeatingStates'; diff --git a/src/pax/core/messages/index.ts b/src/pax/core/messages/index.ts index fca2dd9..bbdb2be 100644 --- a/src/pax/core/messages/index.ts +++ b/src/pax/core/messages/index.ts @@ -6,3 +6,4 @@ export * from './UnknownMessage'; export * from './MessageAbs'; export * from './ReadAndWriteMessageAbs'; export * from './HeatingStateMessage'; +export * from './ColorThemeMessage'; diff --git a/src/pax/core/messages/tests/ColorThemeMessage.test.ts b/src/pax/core/messages/tests/ColorThemeMessage.test.ts index 2cd07ca..fcc932e 100644 --- a/src/pax/core/messages/tests/ColorThemeMessage.test.ts +++ b/src/pax/core/messages/tests/ColorThemeMessage.test.ts @@ -4,7 +4,7 @@ import { describe, expect, it } from 'vitest'; import { ColorThemeMessage } from '../ColorThemeMessage'; -const theme = { +export const theme = { heating: { animation: 0, color1: { diff --git a/src/pax/shared/types/index.ts b/src/pax/shared/types/index.ts index fbf628a..0718daa 100644 --- a/src/pax/shared/types/index.ts +++ b/src/pax/shared/types/index.ts @@ -1 +1,2 @@ export * from './hex'; +export * from './colorTheme'; diff --git a/src/state/paxState/actions.ts b/src/state/paxState/actions.ts index 06a699e..40db6c0 100644 --- a/src/state/paxState/actions.ts +++ b/src/state/paxState/actions.ts @@ -4,12 +4,14 @@ export type PaxActions = | { type: 'SET_ACTUAL_TEMPERATURE'; payload: number } | { type: 'SET_HEATER_SETPOINT_TEMPERATURE'; payload: number } | { type: 'SET_HEATING_STATE'; payload: Pax.lib.HeatingStates } + | { type: 'SET_COLOR_THEME'; payload: Pax.lib.ColorTheme } | { type: 'RESET_PAX_STATE' }; export interface BuiltPaxActions { setActualTemperature: (temperature: number) => void; setHeaterSetPointTemperature: (temperature: number) => void; setHeatingState: (heatingSate: Pax.lib.HeatingStates) => void; + setColorTheme: (theme: Pax.lib.ColorTheme) => void; resetPaxState: () => void; } @@ -38,6 +40,13 @@ const resetPaxState = (dispatch: React.Dispatch) => { dispatch({ type: 'RESET_PAX_STATE' }); }; +const setColorTheme = ( + dispatch: React.Dispatch, + theme: Pax.lib.ColorTheme, +) => { + dispatch({ type: 'SET_COLOR_THEME', payload: theme }); +}; + export const buildActions = ( dispatch: React.Dispatch, ): BuiltPaxActions => { @@ -48,6 +57,8 @@ export const buildActions = ( setHeaterSetPointTemperature(dispatch, temperature), setHeatingState: (heatingSate: Pax.lib.HeatingStates) => setHeatingState(dispatch, heatingSate), + setColorTheme: (theme: Pax.lib.ColorTheme) => + setColorTheme(dispatch, theme), resetPaxState: () => resetPaxState(dispatch), }; }; diff --git a/src/state/paxState/reducer.ts b/src/state/paxState/reducer.ts index 9caa069..97c54c6 100644 --- a/src/state/paxState/reducer.ts +++ b/src/state/paxState/reducer.ts @@ -5,6 +5,7 @@ export const initialPaxState: PaxState = { actualTemperature: 0, heaterSetPointTemperature: 0, heatingSate: undefined, + colorTheme: undefined, }; const reducer = (state: PaxState, action: PaxActions): PaxState => { @@ -15,6 +16,8 @@ const reducer = (state: PaxState, action: PaxActions): PaxState => { return { ...state, heaterSetPointTemperature: action.payload }; case 'SET_HEATING_STATE': return { ...state, heatingSate: action.payload }; + case 'SET_COLOR_THEME': + return { ...state, colorTheme: action.payload }; case 'RESET_PAX_STATE': return initialPaxState; default: diff --git a/src/state/paxState/types.ts b/src/state/paxState/types.ts index 6fedba6..fa9fe2b 100644 --- a/src/state/paxState/types.ts +++ b/src/state/paxState/types.ts @@ -4,4 +4,5 @@ export interface PaxState { actualTemperature: number; heaterSetPointTemperature: number; heatingSate?: Pax.lib.HeatingStates; + colorTheme?: Pax.lib.ColorTheme; }