A design system in a box. hip-ui.tngl.io/docs/introduction
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

range calendar

+944 -4
+5
.cursor/rules/hip-component.mdc
··· 12 12 1. Run the build 13 13 1. Run `pnpm hip install --all` in the apps/docs dir 14 14 1. Generate and .mdx page with example in apps/docs 15 + 16 + ## Rules 17 + 18 + - Prefer using packages/hip-ui/src/components/flex and packages/hip-ui/src/components/grid over css 19 + - Use icon from lucide-react
+2 -1
README.md
··· 33 33 34 34 #### react-aria wrappers 35 35 36 - - [ ] Calendar 37 36 - [ ] Date Picker 38 37 - [ ] Range Date Picker 39 38 ··· 54 53 - [ ] Toolbar 55 54 - [ ] Toast 56 55 56 + - [x] Calendar 57 + - [x] Range Calendar 57 58 - [x] Color Picker 58 59 - [x] Color Swatch Picker 59 60 - [x] Color Wheel
+102
apps/docs/src/components/calendar/index.tsx
··· 1 + import * as stylex from "@stylexjs/stylex"; 2 + import { ChevronLeft, ChevronRight } from "lucide-react"; 3 + import { 4 + Calendar as AriaCalendar, 5 + CalendarCell, 6 + CalendarGrid, 7 + CalendarGridProps, 8 + CalendarGridBody, 9 + CalendarGridHeader, 10 + CalendarHeaderCell, 11 + Heading, 12 + type CalendarProps as AriaCalendarProps, 13 + DateValue, 14 + } from "react-aria-components"; 15 + 16 + import type { StyleXComponentProps } from "../theme/types"; 17 + 18 + import { Flex } from "../flex"; 19 + import { IconButton } from "../icon-button"; 20 + import { ErrorMessage } from "../label"; 21 + import { spacing } from "../theme/spacing.stylex"; 22 + import { useCalendarStyles } from "../theme/useCalendarStyles"; 23 + 24 + export interface CalendarProps<T extends DateValue> 25 + extends StyleXComponentProps<AriaCalendarProps<T>>, 26 + Pick<CalendarGridProps, "weekdayStyle"> { 27 + errorMessage?: string; 28 + } 29 + 30 + const styles = stylex.create({ 31 + root: { 32 + display: "flex", 33 + flexDirection: "column", 34 + gap: spacing["3"], 35 + }, 36 + header: { 37 + alignItems: "center", 38 + display: "flex", 39 + justifyContent: "space-between", 40 + }, 41 + }); 42 + 43 + export function Calendar<T extends DateValue>(props: CalendarProps<T>) { 44 + const { style, errorMessage, weekdayStyle, visibleDuration, ...rest } = props; 45 + const monthsVisible = Array.from({ 46 + length: visibleDuration?.months || 1, 47 + }).map((_, index) => index); 48 + const calendarStyles = useCalendarStyles({ type: "calendar" }); 49 + 50 + return ( 51 + <AriaCalendar 52 + visibleDuration={visibleDuration} 53 + {...rest} 54 + {...stylex.props(styles.root, style)} 55 + > 56 + <header {...stylex.props(styles.header)}> 57 + <IconButton 58 + variant="secondary" 59 + slot="previous" 60 + aria-label="Previous month" 61 + > 62 + <ChevronLeft /> 63 + </IconButton> 64 + <Heading {...stylex.props(calendarStyles.heading)} /> 65 + <IconButton variant="secondary" slot="next" aria-label="Previous month"> 66 + <ChevronRight /> 67 + </IconButton> 68 + </header> 69 + <Flex align="start" gap="4"> 70 + {monthsVisible.map((month) => ( 71 + <CalendarGrid 72 + key={month} 73 + weekdayStyle={weekdayStyle} 74 + offset={{ months: month }} 75 + {...stylex.props(calendarStyles.grid)} 76 + > 77 + <CalendarGridHeader> 78 + {(day) => ( 79 + <CalendarHeaderCell 80 + {...stylex.props(calendarStyles.headerCell)} 81 + > 82 + {day} 83 + </CalendarHeaderCell> 84 + )} 85 + </CalendarGridHeader> 86 + <CalendarGridBody> 87 + {(date) => ( 88 + <CalendarCell 89 + date={date} 90 + {...stylex.props(calendarStyles.cell)} 91 + /> 92 + )} 93 + </CalendarGridBody> 94 + </CalendarGrid> 95 + ))} 96 + </Flex> 97 + {errorMessage && <ErrorMessage>{errorMessage}</ErrorMessage>} 98 + </AriaCalendar> 99 + ); 100 + } 101 + 102 + export default Calendar;
+25 -1
apps/docs/src/components/icon-button/index.tsx
··· 25 25 }, 26 26 }); 27 27 28 - interface IconButtonProps extends StyleXComponentProps<AriaButtonProps> { 28 + interface IconBaseButtonProps extends StyleXComponentProps<AriaButtonProps> { 29 29 variant?: ButtonVariant; 30 30 size?: Size; 31 + } 32 + 33 + interface IconButtonWithLabelProps extends IconBaseButtonProps { 31 34 label: string; 32 35 tooltipOpen?: boolean; 33 36 onTooltipOpenChange?: (isOpen: boolean) => void; 34 37 } 35 38 39 + interface IconButtonWithAriaLabelProps extends IconBaseButtonProps { 40 + "aria-label": string; 41 + label?: never; 42 + tooltipOpen?: never; 43 + onTooltipOpenChange?: never; 44 + } 45 + 46 + type IconButtonProps = IconButtonWithLabelProps | IconButtonWithAriaLabelProps; 47 + 36 48 export const IconButton = ({ 37 49 children, 38 50 size: sizeProp, ··· 43 55 ...props 44 56 }: IconButtonProps) => { 45 57 const size = sizeProp || use(SizeContext); 58 + 59 + if (!label) { 60 + return ( 61 + <Button 62 + size={size} 63 + style={[styles.button as unknown as stylex.StyleXStyles, style]} 64 + {...props} 65 + > 66 + {children} 67 + </Button> 68 + ); 69 + } 46 70 47 71 return ( 48 72 <Tooltip
+102
apps/docs/src/components/range-calendar/index.tsx
··· 1 + import * as stylex from "@stylexjs/stylex"; 2 + import { ChevronLeft, ChevronRight } from "lucide-react"; 3 + import { 4 + RangeCalendar as AriaRangeCalendar, 5 + CalendarCell, 6 + CalendarGrid, 7 + CalendarGridProps, 8 + CalendarGridBody, 9 + CalendarGridHeader, 10 + CalendarHeaderCell, 11 + Heading, 12 + type RangeCalendarProps as AriaRangeCalendarProps, 13 + DateValue, 14 + } from "react-aria-components"; 15 + 16 + import type { StyleXComponentProps } from "../theme/types"; 17 + 18 + import { Flex } from "../flex"; 19 + import { IconButton } from "../icon-button"; 20 + import { ErrorMessage } from "../label"; 21 + import { spacing } from "../theme/spacing.stylex"; 22 + import { useCalendarStyles } from "../theme/useCalendarStyles"; 23 + 24 + export interface RangeCalendarProps<T extends DateValue> 25 + extends StyleXComponentProps<AriaRangeCalendarProps<T>>, 26 + Pick<CalendarGridProps, "weekdayStyle"> { 27 + errorMessage?: string; 28 + } 29 + 30 + const styles = stylex.create({ 31 + root: { 32 + display: "flex", 33 + flexDirection: "column", 34 + gap: spacing["3"], 35 + }, 36 + header: { 37 + alignItems: "center", 38 + display: "flex", 39 + justifyContent: "space-between", 40 + }, 41 + }); 42 + 43 + export function RangeCalendar<T extends DateValue>( 44 + props: RangeCalendarProps<T>, 45 + ) { 46 + const { style, errorMessage, weekdayStyle, visibleDuration, ...rest } = props; 47 + const monthsVisible = Array.from({ 48 + length: visibleDuration?.months || 1, 49 + }).map((_, index) => index); 50 + const calendarStyles = useCalendarStyles({ type: "range-calendar" }); 51 + 52 + return ( 53 + <AriaRangeCalendar 54 + visibleDuration={visibleDuration} 55 + {...rest} 56 + {...stylex.props(styles.root, style)} 57 + > 58 + <header {...stylex.props(styles.header)}> 59 + <IconButton 60 + variant="secondary" 61 + slot="previous" 62 + aria-label="Previous month" 63 + > 64 + <ChevronLeft /> 65 + </IconButton> 66 + <Heading {...stylex.props(calendarStyles.heading)} /> 67 + <IconButton variant="secondary" slot="next" aria-label="Previous month"> 68 + <ChevronRight /> 69 + </IconButton> 70 + </header> 71 + <Flex align="start" gap="4"> 72 + {monthsVisible.map((month) => ( 73 + <CalendarGrid 74 + key={month} 75 + weekdayStyle={weekdayStyle} 76 + offset={{ months: month }} 77 + {...stylex.props(calendarStyles.grid)} 78 + > 79 + <CalendarGridHeader> 80 + {(day) => ( 81 + <CalendarHeaderCell 82 + {...stylex.props(calendarStyles.headerCell)} 83 + > 84 + {day} 85 + </CalendarHeaderCell> 86 + )} 87 + </CalendarGridHeader> 88 + <CalendarGridBody> 89 + {(date) => ( 90 + <CalendarCell 91 + date={date} 92 + {...stylex.props(calendarStyles.cell)} 93 + /> 94 + )} 95 + </CalendarGridBody> 96 + </CalendarGrid> 97 + ))} 98 + </Flex> 99 + {errorMessage && <ErrorMessage>{errorMessage}</ErrorMessage>} 100 + </AriaRangeCalendar> 101 + ); 102 + }
+143
apps/docs/src/components/theme/useCalendarStyles.ts
··· 1 + import * as stylex from "@stylexjs/stylex"; 2 + import { 3 + CalendarGridProps, 4 + type CalendarProps as AriaCalendarProps, 5 + DateValue, 6 + } from "react-aria-components"; 7 + 8 + import type { StyleXComponentProps } from "../theme/types"; 9 + 10 + import { animationDuration } from "../theme/animations.stylex"; 11 + import { radius } from "../theme/radius.stylex"; 12 + import { primaryColor, uiColor } from "../theme/semantic-color.stylex"; 13 + import { spacing } from "../theme/spacing.stylex"; 14 + import { fontSize, fontWeight } from "./typography.stylex"; 15 + 16 + export interface CalendarProps<T extends DateValue> 17 + extends StyleXComponentProps<AriaCalendarProps<T>>, 18 + Pick<CalendarGridProps, "weekdayStyle"> { 19 + errorMessage?: string; 20 + } 21 + 22 + const styles = stylex.create({ 23 + cell: { 24 + borderRadius: radius.md, 25 + color: { 26 + default: uiColor.text1, 27 + ":is([data-hovered]):not([data-unavailable])": uiColor.text2, 28 + ":is([data-selected])": primaryColor.text2, 29 + }, 30 + cursor: "default", 31 + lineHeight: spacing["8"], 32 + opacity: { 33 + ":is([data-outside-visible-range],[data-unavailable])": 0.5, 34 + }, 35 + padding: spacing["1"], 36 + position: "relative", 37 + textAlign: "center", 38 + textDecoration: { 39 + ":is([data-unavailable])": "line-through", 40 + }, 41 + transitionDuration: animationDuration.fast, 42 + transitionProperty: "color", 43 + transitionTimingFunction: "ease-in-out", 44 + width: spacing["8"], 45 + zIndex: 0, 46 + 47 + "::before": { 48 + content: "''", 49 + inset: spacing["1"], 50 + position: "absolute", 51 + transitionDuration: animationDuration.fast, 52 + transitionProperty: "background-color", 53 + transitionTimingFunction: "ease-in-out", 54 + zIndex: -1, 55 + }, 56 + }, 57 + nonRangeCell: { 58 + backgroundColor: { 59 + ":is(*)::before": "transparent", 60 + ":is([data-hovered]):not([data-unavailable])::before": uiColor.component2, 61 + ":is([data-pressed]):not([data-unavailable])::before": uiColor.component3, 62 + ":is([data-selected]):not([data-unavailable])::before": 63 + primaryColor.component2, 64 + ":is([data-selected]):not([data-unavailable]):hover::before": 65 + primaryColor.component3, 66 + }, 67 + borderRadius: { 68 + "::before": radius.md, 69 + }, 70 + color: { 71 + default: uiColor.text1, 72 + ":is([data-hovered]):not([data-unavailable])": uiColor.text2, 73 + ":is([data-selected])": primaryColor.text2, 74 + }, 75 + }, 76 + rangeCell: { 77 + backgroundColor: { 78 + ":is(*)::before": "transparent", 79 + ":is([data-hovered]):not([data-unavailable])::before": uiColor.component2, 80 + ":is([data-pressed]):not([data-unavailable])::before": uiColor.component3, 81 + ":is([data-selected]):not([data-selection-start],[data-selection-end]):not([data-unavailable])::before": 82 + primaryColor.component1, 83 + ":is([data-selection-start],[data-selection-end]):not([data-unavailable])::before": 84 + primaryColor.component2, 85 + ":is([data-selection-start],[data-selection-end]):not([data-unavailable]):hover::before": 86 + primaryColor.component3, 87 + }, 88 + borderBottomLeftRadius: { 89 + ":is([data-selection-start],td:first-child > *)::before": radius.md, 90 + }, 91 + borderBottomRightRadius: { 92 + ":is([data-selection-end],td:last-child > *)::before": radius.md, 93 + }, 94 + borderTopLeftRadius: { 95 + ":is([data-selection-start],td:first-child > *)::before": radius.md, 96 + }, 97 + borderTopRightRadius: { 98 + ":is([data-selection-end],td:last-child > *)::before": radius.md, 99 + }, 100 + color: { 101 + default: uiColor.text1, 102 + ":is([data-hovered]):not([data-unavailable])": uiColor.text2, 103 + ":is([data-selection-start],[data-selection-end])": primaryColor.text2, 104 + }, 105 + marginLeft: { 106 + ":is(td:not(:first-child) > [data-selected]):not([data-selection-start],[data-selection-end])::before": `calc(${spacing["2"]} * -1)`, 107 + }, 108 + marginRight: { 109 + ":is(td:not(:last-child) > [data-selected]):not([data-selection-start],[data-selection-end])::before": `calc(${spacing["2"]} * -1)`, 110 + }, 111 + }, 112 + headerCell: { 113 + fontSize: fontSize["sm"], 114 + fontWeight: fontWeight["medium"], 115 + paddingBottom: spacing["1"], 116 + textAlign: "center", 117 + }, 118 + heading: { 119 + fontSize: fontSize["lg"], 120 + fontWeight: fontWeight["semibold"], 121 + textAlign: "center", 122 + }, 123 + grid: { 124 + borderCollapse: "collapse", 125 + }, 126 + }); 127 + 128 + // eslint-disable-next-line @eslint-react/no-unnecessary-use-prefix 129 + export function useCalendarStyles({ 130 + type, 131 + }: { 132 + type: "calendar" | "range-calendar"; 133 + }) { 134 + return { 135 + grid: [styles.grid], 136 + heading: [styles.heading], 137 + headerCell: [styles.headerCell], 138 + cell: [ 139 + styles.cell, 140 + type === "range-calendar" ? styles.rangeCell : styles.nonRangeCell, 141 + ], 142 + }; 143 + }
+58
apps/docs/src/docs/components/calendar.mdx
··· 1 + --- 2 + title: Calendar 3 + description: A calendar displays one or more date grids and allows users to select a single date. 4 + --- 5 + 6 + import { PropDocs } from '../../lib/PropDocs' 7 + import { Example } from '../../lib/Example' 8 + import { DefaultCalendar } from '../../examples/calendar/default' 9 + import { UnavailableDates } from '../../examples/calendar/unavailable-dates' 10 + import { MultipleMonths } from '../../examples/calendar/multiple-months' 11 + 12 + This component wraps the headless React Aria Calendar with sensible defaults and StyleX tokens. 13 + It renders a header with previous/next buttons and a month heading, and a grid of cells. 14 + 15 + <Example src={DefaultCalendar} /> 16 + 17 + ## Installation 18 + 19 + Run the following command to add the calendar to your project. 20 + 21 + ```bash 22 + pnpm hip install calendar 23 + ``` 24 + 25 + ## Props 26 + 27 + This component is built using the React Aria Calendar. 28 + See the official guide for full behavior and API details. 29 + 30 + - Docs: [React Aria Calendar](https://react-spectrum.adobe.com/react-aria/Calendar.html#reusable-wrappers) 31 + 32 + <PropDocs components={["Calendar"]} /> 33 + 34 + ## Examples 35 + 36 + ### Default 37 + 38 + Basic calendar with month navigation. 39 + 40 + <Example src={DefaultCalendar} /> 41 + 42 + ### Unavailable Dates 43 + 44 + You can mark dates as unavailable by passing an array of dates to the `isDateUnavailable` prop. 45 + 46 + <Example src={UnavailableDates} /> 47 + 48 + ### Multiple Months 49 + 50 + You can display multiple months by passing the `visibleDuration` prop. 51 + 52 + <Example src={MultipleMonths} /> 53 + 54 + ## Related Components 55 + 56 + - [DatePicker](/docs/components/date-picker) - For date input 57 + - [DateRangePicker](/docs/components/date-range-picker) - For date range input 58 + - [DateRangeInput](/docs/components/date-range-input) - For date range input
+55
apps/docs/src/docs/components/range-calendar.mdx
··· 1 + --- 2 + title: Range Calendar 3 + description: A range calendar lets users select a start and end date from one or more month grids. 4 + --- 5 + 6 + import { PropDocs } from '../../lib/PropDocs' 7 + import { Example } from '../../lib/Example' 8 + import { DefaultRangeCalendar } from '../../examples/range-calendar/default' 9 + import { UnavailableRangeDates } from '../../examples/range-calendar/unavailable-dates' 10 + import { MultipleMonthsRange } from '../../examples/range-calendar/multiple-months' 11 + 12 + Select a date range using a calendar grid with previous/next navigation. 13 + Supports marking dates unavailable and showing multiple months. 14 + 15 + <Example src={DefaultRangeCalendar} /> 16 + 17 + ## Installation 18 + 19 + Run the following command to add the range calendar to your project. 20 + 21 + ```bash 22 + pnpm hip install range-calendar 23 + ``` 24 + 25 + ## Props 26 + 27 + This component is built using the React Aria RangeCalendar. 28 + See the official guide for full behavior and API details. 29 + 30 + - Docs: [React Aria RangeCalendar](https://react-spectrum.adobe.com/react-aria/RangeCalendar.html#reusable-wrappers) 31 + 32 + <PropDocs components={["RangeCalendar"]} /> 33 + 34 + ## Examples 35 + 36 + ### Unavailable Dates 37 + 38 + Disable dates via `isDateUnavailable`. 39 + 40 + <Example src={UnavailableRangeDates} /> 41 + 42 + ### Multiple Months 43 + 44 + Show multiple months with `visibleDuration`. 45 + 46 + <Example src={MultipleMonthsRange} /> 47 + 48 + ## Related Components 49 + 50 + - [DateRangePicker](/docs/components/date-range-picker) 51 + - [Calendar](/docs/components/calendar) 52 + - [DateField](/docs/components/date-field) 53 + - [DateRangeField](/docs/components/date-range-field) 54 + 55 +
+7
apps/docs/src/examples/calendar/default.tsx
··· 1 + import { Calendar } from "@/components/calendar"; 2 + 3 + export function DefaultCalendar() { 4 + return <Calendar aria-label="Appointment date" />; 5 + } 6 + 7 +
+11
apps/docs/src/examples/calendar/multiple-months.tsx
··· 1 + import { Calendar } from "@/components/calendar"; 2 + 3 + export function MultipleMonths() { 4 + return ( 5 + <Calendar 6 + aria-label="Pick a date" 7 + visibleDuration={{ months: 2 }} 8 + weekdayStyle="short" 9 + /> 10 + ); 11 + }
+12
apps/docs/src/examples/calendar/unavailable-dates.tsx
··· 1 + import { Calendar } from "@/components/calendar"; 2 + 3 + export function UnavailableDates() { 4 + return ( 5 + <Calendar 6 + aria-label="Pick a date" 7 + isDateUnavailable={(date) => { 8 + return date.day % 2 === 0; 9 + }} 10 + /> 11 + ); 12 + }
+7
apps/docs/src/examples/range-calendar/default.tsx
··· 1 + import { RangeCalendar } from "@/components/range-calendar"; 2 + 3 + export function DefaultRangeCalendar() { 4 + return <RangeCalendar aria-label="Select a date range" />; 5 + } 6 + 7 +
+13
apps/docs/src/examples/range-calendar/multiple-months.tsx
··· 1 + import { RangeCalendar } from "@/components/range-calendar"; 2 + 3 + export function MultipleMonthsRange() { 4 + return ( 5 + <RangeCalendar 6 + aria-label="Select a date range" 7 + visibleDuration={{ months: 2 }} 8 + weekdayStyle="short" 9 + /> 10 + ); 11 + } 12 + 13 +
+10
apps/docs/src/examples/range-calendar/unavailable-dates.tsx
··· 1 + import { RangeCalendar } from "@/components/range-calendar"; 2 + 3 + export function UnavailableRangeDates() { 4 + return ( 5 + <RangeCalendar 6 + aria-label="Select a date range" 7 + isDateUnavailable={(date) => date.day % 2 === 0} 8 + /> 9 + ); 10 + }
+5 -1
packages/hip-ui/src/cli/install.tsx
··· 14 14 import { badgeConfig } from "../components/badge/badge-config.js"; 15 15 import { buttonGroupConfig } from "../components/button-group/button-group-config.js"; 16 16 import { buttonConfig } from "../components/button/button-config.js"; 17 + import { calendarConfig } from "../components/calendar/calendar-config.js"; 17 18 import { cardConfig } from "../components/card/card-config.js"; 18 19 import { checkboxConfig } from "../components/checkbox/checkbox-config.js"; 19 20 import { colorAreaConfig } from "../components/color-area/color-area-config.js"; 20 21 import { colorFieldConfig } from "../components/color-field/color-field-config.js"; 22 + import { colorPickerConfig } from "../components/color-picker/color-picker-config.js"; 21 23 import { colorSliderConfig } from "../components/color-slider/color-slider-config.js"; 22 24 import { colorSwatchPickerConfig } from "../components/color-swatch-picker/color-swatch-picker-config.js"; 23 25 import { colorSwatchConfig } from "../components/color-swatch/link-config.js"; 24 26 import { colorWheelConfig } from "../components/color-wheel/color-wheel-config.js"; 25 - import { colorPickerConfig } from "../components/color-picker/color-picker-config.js"; 26 27 import { comboboxConfig } from "../components/combobox/combobox-config.js"; 27 28 import { commandMenuConfig } from "../components/command-menu/command-menu-config.js"; 28 29 import { contextMenuConfig } from "../components/context-menu/context-menu-config.js"; ··· 44 45 import { popoverConfig } from "../components/popover/popover-config.js"; 45 46 import { progressBarConfig } from "../components/progress-bar/progress-bar-config.js"; 46 47 import { radioConfig } from "../components/radio/radio-config.js"; 48 + import { rangeCalendarConfig } from "../components/range-calendar/range-calendar-config.js"; 47 49 import { searchFieldConfig } from "../components/search-field/search-field-config.js"; 48 50 import { segmentedControlConfig } from "../components/segmented-control/segmented-control-config.js"; 49 51 import { selectConfig } from "../components/select/select-config.js"; ··· 120 122 colorWheelConfig, 121 123 colorSwatchPickerConfig, 122 124 colorPickerConfig, 125 + calendarConfig, 126 + rangeCalendarConfig, 123 127 ]; 124 128 125 129 function StringSetting({
+8
packages/hip-ui/src/components/calendar/calendar-config.ts
··· 1 + import { ComponentConfig } from "../../types"; 2 + 3 + export const calendarConfig: ComponentConfig = { 4 + name: "calendar", 5 + filepath: "./index.tsx", 6 + }; 7 + 8 +
+102
packages/hip-ui/src/components/calendar/index.tsx
··· 1 + import * as stylex from "@stylexjs/stylex"; 2 + import { ChevronLeft, ChevronRight } from "lucide-react"; 3 + import { 4 + Calendar as AriaCalendar, 5 + CalendarCell, 6 + CalendarGrid, 7 + CalendarGridProps, 8 + CalendarGridBody, 9 + CalendarGridHeader, 10 + CalendarHeaderCell, 11 + Heading, 12 + type CalendarProps as AriaCalendarProps, 13 + DateValue, 14 + } from "react-aria-components"; 15 + 16 + import type { StyleXComponentProps } from "../theme/types"; 17 + 18 + import { Flex } from "../flex"; 19 + import { IconButton } from "../icon-button"; 20 + import { ErrorMessage } from "../label"; 21 + import { spacing } from "../theme/spacing.stylex"; 22 + import { useCalendarStyles } from "../theme/useCalendarStyles"; 23 + 24 + export interface CalendarProps<T extends DateValue> 25 + extends StyleXComponentProps<AriaCalendarProps<T>>, 26 + Pick<CalendarGridProps, "weekdayStyle"> { 27 + errorMessage?: string; 28 + } 29 + 30 + const styles = stylex.create({ 31 + root: { 32 + display: "flex", 33 + flexDirection: "column", 34 + gap: spacing["3"], 35 + }, 36 + header: { 37 + alignItems: "center", 38 + display: "flex", 39 + justifyContent: "space-between", 40 + }, 41 + }); 42 + 43 + export function Calendar<T extends DateValue>(props: CalendarProps<T>) { 44 + const { style, errorMessage, weekdayStyle, visibleDuration, ...rest } = props; 45 + const monthsVisible = Array.from({ 46 + length: visibleDuration?.months || 1, 47 + }).map((_, index) => index); 48 + const calendarStyles = useCalendarStyles({ type: "calendar" }); 49 + 50 + return ( 51 + <AriaCalendar 52 + visibleDuration={visibleDuration} 53 + {...rest} 54 + {...stylex.props(styles.root, style)} 55 + > 56 + <header {...stylex.props(styles.header)}> 57 + <IconButton 58 + variant="secondary" 59 + slot="previous" 60 + aria-label="Previous month" 61 + > 62 + <ChevronLeft /> 63 + </IconButton> 64 + <Heading {...stylex.props(calendarStyles.heading)} /> 65 + <IconButton variant="secondary" slot="next" aria-label="Previous month"> 66 + <ChevronRight /> 67 + </IconButton> 68 + </header> 69 + <Flex align="start" gap="4"> 70 + {monthsVisible.map((month) => ( 71 + <CalendarGrid 72 + key={month} 73 + weekdayStyle={weekdayStyle} 74 + offset={{ months: month }} 75 + {...stylex.props(calendarStyles.grid)} 76 + > 77 + <CalendarGridHeader> 78 + {(day) => ( 79 + <CalendarHeaderCell 80 + {...stylex.props(calendarStyles.headerCell)} 81 + > 82 + {day} 83 + </CalendarHeaderCell> 84 + )} 85 + </CalendarGridHeader> 86 + <CalendarGridBody> 87 + {(date) => ( 88 + <CalendarCell 89 + date={date} 90 + {...stylex.props(calendarStyles.cell)} 91 + /> 92 + )} 93 + </CalendarGridBody> 94 + </CalendarGrid> 95 + ))} 96 + </Flex> 97 + {errorMessage && <ErrorMessage>{errorMessage}</ErrorMessage>} 98 + </AriaCalendar> 99 + ); 100 + } 101 + 102 + export default Calendar;
+25 -1
packages/hip-ui/src/components/icon-button/index.tsx
··· 25 25 }, 26 26 }); 27 27 28 - interface IconButtonProps extends StyleXComponentProps<AriaButtonProps> { 28 + interface IconBaseButtonProps extends StyleXComponentProps<AriaButtonProps> { 29 29 variant?: ButtonVariant; 30 30 size?: Size; 31 + } 32 + 33 + interface IconButtonWithLabelProps extends IconBaseButtonProps { 31 34 label: string; 32 35 tooltipOpen?: boolean; 33 36 onTooltipOpenChange?: (isOpen: boolean) => void; 34 37 } 35 38 39 + interface IconButtonWithAriaLabelProps extends IconBaseButtonProps { 40 + "aria-label": string; 41 + label?: never; 42 + tooltipOpen?: never; 43 + onTooltipOpenChange?: never; 44 + } 45 + 46 + type IconButtonProps = IconButtonWithLabelProps | IconButtonWithAriaLabelProps; 47 + 36 48 export const IconButton = ({ 37 49 children, 38 50 size: sizeProp, ··· 43 55 ...props 44 56 }: IconButtonProps) => { 45 57 const size = sizeProp || use(SizeContext); 58 + 59 + if (!label) { 60 + return ( 61 + <Button 62 + size={size} 63 + style={[styles.button as unknown as stylex.StyleXStyles, style]} 64 + {...props} 65 + > 66 + {children} 67 + </Button> 68 + ); 69 + } 46 70 47 71 return ( 48 72 <Tooltip
+102
packages/hip-ui/src/components/range-calendar/index.tsx
··· 1 + import * as stylex from "@stylexjs/stylex"; 2 + import { ChevronLeft, ChevronRight } from "lucide-react"; 3 + import { 4 + RangeCalendar as AriaRangeCalendar, 5 + CalendarCell, 6 + CalendarGrid, 7 + CalendarGridProps, 8 + CalendarGridBody, 9 + CalendarGridHeader, 10 + CalendarHeaderCell, 11 + Heading, 12 + type RangeCalendarProps as AriaRangeCalendarProps, 13 + DateValue, 14 + } from "react-aria-components"; 15 + 16 + import type { StyleXComponentProps } from "../theme/types"; 17 + 18 + import { Flex } from "../flex"; 19 + import { IconButton } from "../icon-button"; 20 + import { ErrorMessage } from "../label"; 21 + import { spacing } from "../theme/spacing.stylex"; 22 + import { useCalendarStyles } from "../theme/useCalendarStyles"; 23 + 24 + export interface RangeCalendarProps<T extends DateValue> 25 + extends StyleXComponentProps<AriaRangeCalendarProps<T>>, 26 + Pick<CalendarGridProps, "weekdayStyle"> { 27 + errorMessage?: string; 28 + } 29 + 30 + const styles = stylex.create({ 31 + root: { 32 + display: "flex", 33 + flexDirection: "column", 34 + gap: spacing["3"], 35 + }, 36 + header: { 37 + alignItems: "center", 38 + display: "flex", 39 + justifyContent: "space-between", 40 + }, 41 + }); 42 + 43 + export function RangeCalendar<T extends DateValue>( 44 + props: RangeCalendarProps<T>, 45 + ) { 46 + const { style, errorMessage, weekdayStyle, visibleDuration, ...rest } = props; 47 + const monthsVisible = Array.from({ 48 + length: visibleDuration?.months || 1, 49 + }).map((_, index) => index); 50 + const calendarStyles = useCalendarStyles({ type: "range-calendar" }); 51 + 52 + return ( 53 + <AriaRangeCalendar 54 + visibleDuration={visibleDuration} 55 + {...rest} 56 + {...stylex.props(styles.root, style)} 57 + > 58 + <header {...stylex.props(styles.header)}> 59 + <IconButton 60 + variant="secondary" 61 + slot="previous" 62 + aria-label="Previous month" 63 + > 64 + <ChevronLeft /> 65 + </IconButton> 66 + <Heading {...stylex.props(calendarStyles.heading)} /> 67 + <IconButton variant="secondary" slot="next" aria-label="Previous month"> 68 + <ChevronRight /> 69 + </IconButton> 70 + </header> 71 + <Flex align="start" gap="4"> 72 + {monthsVisible.map((month) => ( 73 + <CalendarGrid 74 + key={month} 75 + weekdayStyle={weekdayStyle} 76 + offset={{ months: month }} 77 + {...stylex.props(calendarStyles.grid)} 78 + > 79 + <CalendarGridHeader> 80 + {(day) => ( 81 + <CalendarHeaderCell 82 + {...stylex.props(calendarStyles.headerCell)} 83 + > 84 + {day} 85 + </CalendarHeaderCell> 86 + )} 87 + </CalendarGridHeader> 88 + <CalendarGridBody> 89 + {(date) => ( 90 + <CalendarCell 91 + date={date} 92 + {...stylex.props(calendarStyles.cell)} 93 + /> 94 + )} 95 + </CalendarGridBody> 96 + </CalendarGrid> 97 + ))} 98 + </Flex> 99 + {errorMessage && <ErrorMessage>{errorMessage}</ErrorMessage>} 100 + </AriaRangeCalendar> 101 + ); 102 + }
+7
packages/hip-ui/src/components/range-calendar/range-calendar-config.ts
··· 1 + import { ComponentConfig } from "../../types"; 2 + 3 + export const rangeCalendarConfig: ComponentConfig = { 4 + name: "range-calendar", 5 + filepath: "./index.tsx", 6 + hipDependencies: ["../theme/useCalendarStyles.ts"], 7 + };
+143
packages/hip-ui/src/components/theme/useCalendarStyles.ts
··· 1 + import * as stylex from "@stylexjs/stylex"; 2 + import { 3 + CalendarGridProps, 4 + type CalendarProps as AriaCalendarProps, 5 + DateValue, 6 + } from "react-aria-components"; 7 + 8 + import type { StyleXComponentProps } from "../theme/types"; 9 + 10 + import { animationDuration } from "../theme/animations.stylex"; 11 + import { radius } from "../theme/radius.stylex"; 12 + import { primaryColor, uiColor } from "../theme/semantic-color.stylex"; 13 + import { spacing } from "../theme/spacing.stylex"; 14 + import { fontSize, fontWeight } from "./typography.stylex"; 15 + 16 + export interface CalendarProps<T extends DateValue> 17 + extends StyleXComponentProps<AriaCalendarProps<T>>, 18 + Pick<CalendarGridProps, "weekdayStyle"> { 19 + errorMessage?: string; 20 + } 21 + 22 + const styles = stylex.create({ 23 + cell: { 24 + borderRadius: radius.md, 25 + color: { 26 + default: uiColor.text1, 27 + ":is([data-hovered]):not([data-unavailable])": uiColor.text2, 28 + ":is([data-selected])": primaryColor.text2, 29 + }, 30 + cursor: "default", 31 + lineHeight: spacing["8"], 32 + opacity: { 33 + ":is([data-outside-visible-range],[data-unavailable])": 0.5, 34 + }, 35 + padding: spacing["1"], 36 + position: "relative", 37 + textAlign: "center", 38 + textDecoration: { 39 + ":is([data-unavailable])": "line-through", 40 + }, 41 + transitionDuration: animationDuration.fast, 42 + transitionProperty: "color", 43 + transitionTimingFunction: "ease-in-out", 44 + width: spacing["8"], 45 + zIndex: 0, 46 + 47 + "::before": { 48 + content: "''", 49 + inset: spacing["1"], 50 + position: "absolute", 51 + transitionDuration: animationDuration.fast, 52 + transitionProperty: "background-color", 53 + transitionTimingFunction: "ease-in-out", 54 + zIndex: -1, 55 + }, 56 + }, 57 + nonRangeCell: { 58 + backgroundColor: { 59 + ":is(*)::before": "transparent", 60 + ":is([data-hovered]):not([data-unavailable])::before": uiColor.component2, 61 + ":is([data-pressed]):not([data-unavailable])::before": uiColor.component3, 62 + ":is([data-selected]):not([data-unavailable])::before": 63 + primaryColor.component2, 64 + ":is([data-selected]):not([data-unavailable]):hover::before": 65 + primaryColor.component3, 66 + }, 67 + borderRadius: { 68 + "::before": radius.md, 69 + }, 70 + color: { 71 + default: uiColor.text1, 72 + ":is([data-hovered]):not([data-unavailable])": uiColor.text2, 73 + ":is([data-selected])": primaryColor.text2, 74 + }, 75 + }, 76 + rangeCell: { 77 + backgroundColor: { 78 + ":is(*)::before": "transparent", 79 + ":is([data-hovered]):not([data-unavailable])::before": uiColor.component2, 80 + ":is([data-pressed]):not([data-unavailable])::before": uiColor.component3, 81 + ":is([data-selected]):not([data-selection-start],[data-selection-end]):not([data-unavailable])::before": 82 + primaryColor.component1, 83 + ":is([data-selection-start],[data-selection-end]):not([data-unavailable])::before": 84 + primaryColor.component2, 85 + ":is([data-selection-start],[data-selection-end]):not([data-unavailable]):hover::before": 86 + primaryColor.component3, 87 + }, 88 + borderBottomLeftRadius: { 89 + ":is([data-selection-start],td:first-child > *)::before": radius.md, 90 + }, 91 + borderBottomRightRadius: { 92 + ":is([data-selection-end],td:last-child > *)::before": radius.md, 93 + }, 94 + borderTopLeftRadius: { 95 + ":is([data-selection-start],td:first-child > *)::before": radius.md, 96 + }, 97 + borderTopRightRadius: { 98 + ":is([data-selection-end],td:last-child > *)::before": radius.md, 99 + }, 100 + color: { 101 + default: uiColor.text1, 102 + ":is([data-hovered]):not([data-unavailable])": uiColor.text2, 103 + ":is([data-selection-start],[data-selection-end])": primaryColor.text2, 104 + }, 105 + marginLeft: { 106 + ":is(td:not(:first-child) > [data-selected]):not([data-selection-start],[data-selection-end])::before": `calc(${spacing["2"]} * -1)`, 107 + }, 108 + marginRight: { 109 + ":is(td:not(:last-child) > [data-selected]):not([data-selection-start],[data-selection-end])::before": `calc(${spacing["2"]} * -1)`, 110 + }, 111 + }, 112 + headerCell: { 113 + fontSize: fontSize["sm"], 114 + fontWeight: fontWeight["medium"], 115 + paddingBottom: spacing["1"], 116 + textAlign: "center", 117 + }, 118 + heading: { 119 + fontSize: fontSize["lg"], 120 + fontWeight: fontWeight["semibold"], 121 + textAlign: "center", 122 + }, 123 + grid: { 124 + borderCollapse: "collapse", 125 + }, 126 + }); 127 + 128 + // eslint-disable-next-line @eslint-react/no-unnecessary-use-prefix 129 + export function useCalendarStyles({ 130 + type, 131 + }: { 132 + type: "calendar" | "range-calendar"; 133 + }) { 134 + return { 135 + grid: [styles.grid], 136 + heading: [styles.heading], 137 + headerCell: [styles.headerCell], 138 + cell: [ 139 + styles.cell, 140 + type === "range-calendar" ? styles.rangeCell : styles.nonRangeCell, 141 + ], 142 + }; 143 + }