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.

combobox

+470 -48
+13 -10
README.md
··· 2 2 3 3 ## TODO 4 4 5 + ### Imprrovements 6 + 7 + - [ ] Add Virtualizer 8 + - [ ] add borderless variant for input fields 9 + 5 10 ### Components 6 11 7 12 ## Maybe ··· 27 32 28 33 #### react-aria wrappers 29 34 35 + - [ ] File Trigger 36 + - [ ] File Drop zone 37 + - [ ] Segmented Control 38 + - [ ] Switch 39 + - [ ] Slider 40 + 30 41 - [ ] Alert Dialog 31 42 - [ ] Breadcrumb 32 43 - [ ] Calendar ··· 35 46 - [ ] Color Swatch 36 47 - [ ] Color Swatch Picker 37 48 - [ ] Color Wheel 38 - - [ ] Combobox 39 49 - [ ] Command 40 50 - [ ] Data Table 41 51 - [ ] Date Picker ··· 43 53 - [ ] Disclosure 44 54 - [ ] Disclosure Group 45 55 - [ ] Drawer 46 - - [ ] File Trigger 47 - - [ ] File Drop zone 48 56 - [ ] Field 49 57 - [ ] Form 50 58 - [ ] Grid List ··· 55 63 - [ ] Navigation Menu 56 64 - [ ] Progress 57 65 - [ ] Range Date Picker 58 - - [ ] Segmented Control 59 66 - [ ] Sheet 60 - - [ ] Slider 61 67 - [ ] Spinner 62 - - [ ] Switch 63 68 - [ ] Table 64 69 - [ ] Tabs 65 70 - [ ] Tag Group 66 71 - [ ] Toolbar 67 72 - [ ] Toast 73 + 74 + - [x] Combobox 68 75 - [x] Number Field 69 76 - [x] Color Field 70 77 - [x] Search Field ··· 92 99 - [x] Tooltip 93 100 - [x] Tree 94 101 - [x] Typography 95 - 96 - ### Imprrovements 97 - 98 - - [ ] Add Virtualizer
+18 -2
apps/example/src/components/KitchenSink.tsx
··· 14 14 Check, 15 15 CpuIcon, 16 16 Ellipsis, 17 + GlobeIcon, 17 18 Pin, 18 19 Plus, 19 20 Search, ··· 70 71 import { SearchField } from "./search-field"; 71 72 import { ColorField } from "./color-field"; 72 73 import { NumberField } from "./number-field"; 74 + import { ComboBox, ComboBoxItem } from "./combobox"; 73 75 74 76 const styles = stylex.create({ 75 77 subCard: { ··· 335 337 prefix={<CpuIcon />} 336 338 defaultValue={1} 337 339 /> 338 - <TimeField label="Time Field" size="lg" /> 339 - <DateField label="Date Field" size="lg" /> 340 + <TimeField label="Time Field" /> 341 + <DateField label="Date Field" /> 342 + <ComboBox label="ComboBox" placeholder="Select an option"> 343 + <ComboBoxItem>ComboBox Item 1</ComboBoxItem> 344 + <ComboBoxItem prefix={<GlobeIcon />} suffix={<div>new</div>}> 345 + ComboBox Item 2 346 + </ComboBoxItem> 347 + <ComboBoxItem isDisabled>ComboBox Item 2</ComboBoxItem> 348 + <ComboBoxItem>ComboBox Item 3</ComboBoxItem> 349 + <ComboBoxItem prefix={<Plus />}> 350 + ComboBox Item 4 351 + <SubLabel variant="secondary"> 352 + This is a description for ComboBox Item 4 353 + </SubLabel> 354 + </ComboBoxItem> 355 + </ComboBox> 340 356 </Flex> 341 357 ); 342 358 }
+125
apps/example/src/components/combobox/index.tsx
··· 1 + import { 2 + Button, 3 + Popover, 4 + PopoverProps, 5 + ComboBox as AriaComboBox, 6 + ComboBoxProps as AriaComboBoxProps, 7 + Input, 8 + } from "react-aria-components"; 9 + import * as stylex from "@stylexjs/stylex"; 10 + import type { 11 + ListBoxSectionProps, 12 + ValidationResult, 13 + } from "react-aria-components"; 14 + import { FieldError } from "react-aria-components"; 15 + import { Description, Label } from "../label"; 16 + import { ChevronDown } from "lucide-react"; 17 + import { Size } from "../types"; 18 + import { 19 + ListBox, 20 + ListBoxItem, 21 + ListBoxItemProps, 22 + ListBoxSectionHeaderProps, 23 + ListBoxSectionHeader, 24 + ListBoxSeparatorProps, 25 + ListBoxSeparator, 26 + ListBoxSection, 27 + } from "../listbox"; 28 + import { SizeContext } from "../context"; 29 + import { useInputStyles } from "../theme/useInputStyles"; 30 + import { usePopoverStyles } from "../theme/usePopoverStyles"; 31 + import { IconButton } from "../icon-button"; 32 + 33 + const styles = stylex.create({ 34 + matchWidth: { 35 + width: "var(--trigger-width)", 36 + }, 37 + }); 38 + 39 + export interface ComboBoxProps<T extends object> 40 + extends Omit<AriaComboBoxProps<T>, "children" | "style" | "className">, 41 + Pick< 42 + PopoverProps, 43 + | "shouldCloseOnInteractOutside" 44 + | "shouldFlip" 45 + | "shouldUpdatePosition" 46 + | "placement" 47 + > { 48 + style?: stylex.StyleXStyles | stylex.StyleXStyles[]; 49 + label?: string; 50 + description?: string; 51 + errorMessage?: string | ((validation: ValidationResult) => string); 52 + items?: Iterable<T>; 53 + children: React.ReactNode | ((item: T) => React.ReactNode); 54 + size?: Size; 55 + placeholder?: string; 56 + prefix?: React.ReactNode; 57 + suffix?: React.ReactNode; 58 + } 59 + 60 + export function ComboBox<T extends object>({ 61 + label, 62 + description, 63 + errorMessage, 64 + children, 65 + items, 66 + style, 67 + size = "md", 68 + shouldCloseOnInteractOutside, 69 + shouldFlip, 70 + shouldUpdatePosition, 71 + placement, 72 + placeholder = "Select an option", 73 + prefix, 74 + suffix, 75 + ...props 76 + }: ComboBoxProps<T>) { 77 + const inputStyles = useInputStyles({ size }); 78 + const popoverStyles = usePopoverStyles(); 79 + 80 + return ( 81 + <SizeContext.Provider value={size}> 82 + <AriaComboBox {...props} {...stylex.props(inputStyles.field, style)}> 83 + {label && <Label size={size}>{label}</Label>} 84 + <Button {...stylex.props(inputStyles.wrapper)}> 85 + {prefix && <div {...stylex.props(inputStyles.addon)}>{prefix}</div>} 86 + <Input 87 + {...stylex.props(inputStyles.input)} 88 + placeholder={placeholder} 89 + /> 90 + {suffix && <div {...stylex.props(inputStyles.addon)}>{suffix}</div>} 91 + <div {...stylex.props(inputStyles.addon)}> 92 + <IconButton size="sm" variant="secondary" label="Open combobox"> 93 + <ChevronDown size={16} aria-hidden="true" /> 94 + </IconButton> 95 + </div> 96 + </Button> 97 + {description && <Description size={size}>{description}</Description>} 98 + <FieldError>{errorMessage}</FieldError> 99 + <Popover 100 + containerPadding={8} 101 + shouldCloseOnInteractOutside={shouldCloseOnInteractOutside} 102 + shouldFlip={shouldFlip} 103 + shouldUpdatePosition={shouldUpdatePosition} 104 + placement={placement} 105 + > 106 + <ListBox 107 + items={items} 108 + {...stylex.props(popoverStyles, styles.matchWidth)} 109 + > 110 + {children} 111 + </ListBox> 112 + </Popover> 113 + </AriaComboBox> 114 + </SizeContext.Provider> 115 + ); 116 + } 117 + 118 + export type ComboBoxItemProps = ListBoxItemProps; 119 + export const ComboBoxItem = ListBoxItem; 120 + export type ComboBoxSectionProps<T extends object> = ListBoxSectionProps<T>; 121 + export const ComboBoxSection = ListBoxSection; 122 + export type ComboBoxSectionHeaderProps = ListBoxSectionHeaderProps; 123 + export const ComboBoxSectionHeader = ListBoxSectionHeader; 124 + export type ComboBoxSeparatorProps = ListBoxSeparatorProps; 125 + export const ComboBoxSeparator = ListBoxSeparator;
+65 -9
apps/example/src/components/listbox/index.tsx
··· 13 13 import { spacing } from "../theme/spacing.stylex"; 14 14 import { plum, slate } from "../theme/colors.stylex"; 15 15 import { radius } from "../theme/radius.stylex"; 16 - import { fontWeight, typeramp } from "../theme/typography.stylex"; 16 + import { 17 + fontSize, 18 + fontWeight, 19 + lineHeight, 20 + typeramp, 21 + } from "../theme/typography.stylex"; 17 22 import { Size } from "../types"; 18 23 import { SizeContext } from "../context"; 19 24 import { use, useContext } from "react"; ··· 37 42 padding: spacing["1"], 38 43 }, 39 44 sm: { 40 - height: spacing["9"], 45 + minHeight: spacing["9"], 41 46 }, 42 47 md: { 43 - height: spacing["9"], 48 + minHeight: spacing["9"], 44 49 }, 45 50 lg: { 46 - height: spacing["11"], 51 + minHeight: spacing["11"], 47 52 }, 48 53 itemInner: { 49 54 alignItems: "center", 50 55 backgroundColor: { 51 56 default: "transparent", 52 - [":is(:hover > *)"]: slate[4], 57 + [":is(:hover:not([data-disabled]) > *,[data-focused] > *)"]: slate[4], 53 58 [":is(:active > *)"]: slate[5], 54 59 }, 55 60 borderRadius: radius["md"], 56 61 boxSizing: "border-box", 62 + color: { 63 + default: slate[12], 64 + [":is([data-disabled] > *)"]: slate[8], 65 + }, 57 66 display: "flex", 58 67 flexGrow: 1, 59 68 gap: spacing["2"], 60 - justifyContent: "space-between", 69 + paddingBottom: spacing["2"], 61 70 paddingLeft: spacing["2"], 62 71 paddingRight: spacing["2"], 72 + paddingTop: spacing["2"], 63 73 transitionDuration: "100ms", 64 74 transitionProperty: "background-color", 65 75 transitionTimingFunction: "ease-in-out", 76 + }, 77 + smItemInner: { 78 + fontSize: fontSize["xs"], 79 + lineHeight: lineHeight["xs"], 80 + paddingBottom: spacing["1"], 81 + paddingTop: spacing["1"], 66 82 }, 67 83 check: { 68 84 color: plum[9], ··· 90 106 marginBottom: spacing["1.5"], 91 107 marginTop: spacing["1.5"], 92 108 }, 109 + addon: { 110 + alignItems: "center", 111 + display: "flex", 112 + justifyContent: "center", 113 + minWidth: spacing["4"], 114 + 115 + // eslint-disable-next-line @stylexjs/no-legacy-contextual-styles, @stylexjs/valid-styles 116 + ":is(*) svg": { 117 + color: slate[11], 118 + flexShrink: 0, 119 + height: spacing["4"], 120 + pointerEvents: "none", 121 + width: spacing["4"], 122 + }, 123 + }, 124 + label: { 125 + display: "flex", 126 + flexDirection: "column", 127 + flexGrow: 1, 128 + gap: spacing["1.5"], 129 + }, 93 130 }); 94 131 95 132 export interface ListBoxProps<T extends object> ··· 118 155 extends Omit<AriaListBoxItemProps, "style" | "className" | "children"> { 119 156 style?: stylex.StyleXStyles | stylex.StyleXStyles[]; 120 157 children: React.ReactNode; 158 + prefix?: React.ReactNode; 159 + suffix?: React.ReactNode; 121 160 } 122 161 123 - export function ListBoxItem({ style, children, ...props }: ListBoxItemProps) { 162 + export function ListBoxItem({ 163 + style, 164 + children, 165 + prefix, 166 + suffix, 167 + ...props 168 + }: ListBoxItemProps) { 124 169 const size = useContext(SizeContext); 125 170 126 171 return ( 127 172 <AriaListBoxItem 128 173 {...props} 174 + value={props.value || { id: props.id, label: children }} 175 + textValue={ 176 + props.textValue || (typeof children === "string" ? children : undefined) 177 + } 129 178 {...stylex.props(typeramp.label, styles.item, styles[size], style)} 130 179 > 131 180 {({ isSelected }) => ( 132 - <div {...stylex.props(styles.itemInner)}> 133 - {children} 181 + <div 182 + {...stylex.props( 183 + styles.itemInner, 184 + size === "sm" && styles.smItemInner 185 + )} 186 + > 187 + {prefix && <div {...stylex.props(styles.addon)}>{prefix}</div>} 188 + <div {...stylex.props(styles.label)}>{children}</div> 189 + {suffix && <div {...stylex.props(styles.addon)}>{suffix}</div>} 134 190 {isSelected && <Check size={16} {...stylex.props(styles.check)} />} 135 191 </div> 136 192 )}
+7 -16
apps/example/src/components/number-field/index.tsx
··· 75 75 }: NumberFieldProps) { 76 76 const inputRef = useRef<HTMLInputElement>(null); 77 77 const inputStyles = useInputStyles({ size }); 78 + const buttonStyles = stylex.props( 79 + styles.button, 80 + gray.borderInteractive, 81 + gray.bgAction 82 + ); 78 83 79 84 return ( 80 85 <AriaNumberField {...props} {...stylex.props(inputStyles.field, style)}> ··· 91 96 /> 92 97 {suffix && <div {...stylex.props(inputStyles.addon)}>{suffix}</div>} 93 98 <Group {...stylex.props(styles.buttons)}> 94 - <Button 95 - slot="decrement" 96 - {...stylex.props( 97 - styles.button, 98 - gray.borderInteractive, 99 - gray.bgAction 100 - )} 101 - > 99 + <Button slot="decrement" {...buttonStyles}> 102 100 <Minus /> 103 101 </Button> 104 - <Button 105 - slot="increment" 106 - {...stylex.props( 107 - styles.button, 108 - gray.borderInteractive, 109 - gray.bgAction 110 - )} 111 - > 102 + <Button slot="increment" {...buttonStyles}> 112 103 <Plus /> 113 104 </Button> 114 105 </Group>
+6
apps/example/src/components/select/index.tsx
··· 52 52 children: React.ReactNode | ((item: T) => React.ReactNode); 53 53 size?: Size; 54 54 placeholder?: string; 55 + prefix?: React.ReactNode; 56 + suffix?: React.ReactNode; 55 57 } 56 58 57 59 export function Select< ··· 70 72 shouldUpdatePosition, 71 73 placement, 72 74 placeholder = "Select an option", 75 + prefix, 76 + suffix, 73 77 ...props 74 78 }: SelectProps<T, M>) { 75 79 const inputStyles = useInputStyles({ size }); ··· 84 88 > 85 89 {label && <Label size={size}>{label}</Label>} 86 90 <Button {...stylex.props(inputStyles.wrapper)}> 91 + {prefix && <div {...stylex.props(inputStyles.addon)}>{prefix}</div>} 87 92 <SelectValue {...stylex.props(inputStyles.input)}> 88 93 {({ selectedText, isPlaceholder, defaultChildren }) => { 89 94 if (isPlaceholder) return placeholder; ··· 92 97 return defaultChildren; 93 98 }} 94 99 </SelectValue> 100 + {suffix && <div {...stylex.props(inputStyles.addon)}>{suffix}</div>} 95 101 <div {...stylex.props(inputStyles.addon)}> 96 102 <ChevronDown size={16} aria-hidden="true" /> 97 103 </div>
+1 -1
apps/example/src/components/theme/useInputStyles.ts
··· 101 101 export function useInputStyles({ size = "md" }: { size?: Size }) { 102 102 return { 103 103 field: [styles.field], 104 - wrapper: [styles.inputWrapper, gray.text, styles[size]], 104 + wrapper: [styles.inputWrapper, gray.bgUi, gray.text, styles[size]], 105 105 input: [styles.input, styles[`${size}Input`]], 106 106 addon: [styles.addon], 107 107 };
+2
packages/hip-ui/src/cli/install.tsx
··· 35 35 import { searchFieldConfig } from "../components/search-field/search-field-config.js"; 36 36 import { colorFieldConfig } from "../components/color-field/color-field-config.js"; 37 37 import { numberFieldConfig } from "../components/number-field/number-field-config.js"; 38 + import { comboboxConfig } from "../components/combobox/combobox-config.js"; 38 39 39 40 const __dirname = path.dirname(new URL(import.meta.url).pathname); 40 41 ··· 65 66 searchFieldConfig, 66 67 colorFieldConfig, 67 68 numberFieldConfig, 69 + comboboxConfig, 68 70 ]; 69 71 70 72 function StringSetting({
+17
packages/hip-ui/src/components/combobox/combobox-config.ts
··· 1 + import { ComponentConfig } from "../../types"; 2 + 3 + export const comboboxConfig: ComponentConfig = { 4 + name: "combobox", 5 + filepath: "./index.tsx", 6 + hipDependencies: [ 7 + "../theme/spacing.stylex.tsx", 8 + "../theme/colors.stylex.tsx", 9 + "../theme/radius.stylex.tsx", 10 + "../theme/semantic-color.stylex.tsx", 11 + "../theme/typography.stylex.tsx", 12 + "../theme/breakpoints.stylex.tsx", 13 + "../theme/shadow.stylex.tsx", 14 + "../theme/useInputStyles.ts", 15 + "../theme/usePopoverStyles.ts", 16 + ], 17 + };
+144
packages/hip-ui/src/components/combobox/index.tsx
··· 1 + import { 2 + Button, 3 + Popover, 4 + PopoverProps, 5 + ComboBox as AriaComboBox, 6 + ComboBoxProps as AriaComboBoxProps, 7 + Input, 8 + } from "react-aria-components"; 9 + import * as stylex from "@stylexjs/stylex"; 10 + import type { 11 + ListBoxProps, 12 + ListBoxSectionProps, 13 + ValidationResult, 14 + } from "react-aria-components"; 15 + import { FieldError } from "react-aria-components"; 16 + import { Description, Label } from "../label"; 17 + import { ChevronDown } from "lucide-react"; 18 + import { Size } from "../types"; 19 + import { 20 + ListBox, 21 + ListBoxItem, 22 + ListBoxItemProps, 23 + ListBoxSectionHeaderProps, 24 + ListBoxSectionHeader, 25 + ListBoxSeparatorProps, 26 + ListBoxSeparator, 27 + ListBoxSection, 28 + } from "../listbox"; 29 + import { SizeContext } from "../context"; 30 + import { useInputStyles } from "../theme/useInputStyles"; 31 + import { usePopoverStyles } from "../theme/usePopoverStyles"; 32 + import { IconButton } from "../icon-button"; 33 + import { SmallBody } from "../typography"; 34 + import { spacing } from "../theme/spacing.stylex"; 35 + 36 + const styles = stylex.create({ 37 + matchWidth: { 38 + width: "var(--trigger-width)", 39 + }, 40 + emptyState: { 41 + display: "flex", 42 + justifyContent: "center", 43 + padding: spacing["4"], 44 + }, 45 + }); 46 + 47 + function EmptyState() { 48 + return ( 49 + <div {...stylex.props(styles.emptyState)}> 50 + <SmallBody variant="secondary">No items found</SmallBody> 51 + </div> 52 + ); 53 + } 54 + 55 + export interface ComboBoxProps<T extends object> 56 + extends Omit<AriaComboBoxProps<T>, "children" | "style" | "className">, 57 + Pick< 58 + PopoverProps, 59 + | "shouldCloseOnInteractOutside" 60 + | "shouldFlip" 61 + | "shouldUpdatePosition" 62 + | "placement" 63 + >, 64 + Pick<ListBoxProps<T>, "renderEmptyState"> { 65 + style?: stylex.StyleXStyles | stylex.StyleXStyles[]; 66 + label?: string; 67 + description?: string; 68 + errorMessage?: string | ((validation: ValidationResult) => string); 69 + items?: Iterable<T>; 70 + children: React.ReactNode | ((item: T) => React.ReactNode); 71 + size?: Size; 72 + placeholder?: string; 73 + prefix?: React.ReactNode; 74 + suffix?: React.ReactNode; 75 + } 76 + 77 + export function ComboBox<T extends object>({ 78 + label, 79 + description, 80 + errorMessage, 81 + children, 82 + items, 83 + style, 84 + size = "md", 85 + shouldCloseOnInteractOutside, 86 + shouldFlip, 87 + shouldUpdatePosition, 88 + placement, 89 + placeholder = "Select an option", 90 + prefix, 91 + suffix, 92 + renderEmptyState, 93 + ...props 94 + }: ComboBoxProps<T>) { 95 + const inputStyles = useInputStyles({ size }); 96 + const popoverStyles = usePopoverStyles(); 97 + 98 + return ( 99 + <SizeContext.Provider value={size}> 100 + <AriaComboBox {...props} {...stylex.props(inputStyles.field, style)}> 101 + {label && <Label size={size}>{label}</Label>} 102 + <Button {...stylex.props(inputStyles.wrapper)}> 103 + {prefix && <div {...stylex.props(inputStyles.addon)}>{prefix}</div>} 104 + <Input 105 + {...stylex.props(inputStyles.input)} 106 + placeholder={placeholder} 107 + /> 108 + {suffix && <div {...stylex.props(inputStyles.addon)}>{suffix}</div>} 109 + <div {...stylex.props(inputStyles.addon)}> 110 + <IconButton size="sm" variant="secondary" label="Open combobox"> 111 + <ChevronDown size={16} aria-hidden="true" /> 112 + </IconButton> 113 + </div> 114 + </Button> 115 + {description && <Description size={size}>{description}</Description>} 116 + <FieldError>{errorMessage}</FieldError> 117 + <Popover 118 + containerPadding={8} 119 + shouldCloseOnInteractOutside={shouldCloseOnInteractOutside} 120 + shouldFlip={shouldFlip} 121 + shouldUpdatePosition={shouldUpdatePosition} 122 + placement={placement} 123 + > 124 + <ListBox 125 + items={items} 126 + {...stylex.props(popoverStyles, styles.matchWidth)} 127 + renderEmptyState={renderEmptyState || EmptyState} 128 + > 129 + {children} 130 + </ListBox> 131 + </Popover> 132 + </AriaComboBox> 133 + </SizeContext.Provider> 134 + ); 135 + } 136 + 137 + export type ComboBoxItemProps = ListBoxItemProps; 138 + export const ComboBoxItem = ListBoxItem; 139 + export type ComboBoxSectionProps<T extends object> = ListBoxSectionProps<T>; 140 + export const ComboBoxSection = ListBoxSection; 141 + export type ComboBoxSectionHeaderProps = ListBoxSectionHeaderProps; 142 + export const ComboBoxSectionHeader = ListBoxSectionHeader; 143 + export type ComboBoxSeparatorProps = ListBoxSeparatorProps; 144 + export const ComboBoxSeparator = ListBoxSeparator;
+65 -9
packages/hip-ui/src/components/listbox/index.tsx
··· 13 13 import { spacing } from "../theme/spacing.stylex"; 14 14 import { plum, slate } from "../theme/colors.stylex"; 15 15 import { radius } from "../theme/radius.stylex"; 16 - import { fontWeight, typeramp } from "../theme/typography.stylex"; 16 + import { 17 + fontSize, 18 + fontWeight, 19 + lineHeight, 20 + typeramp, 21 + } from "../theme/typography.stylex"; 17 22 import { Size } from "../types"; 18 23 import { SizeContext } from "../context"; 19 24 import { use, useContext } from "react"; ··· 37 42 padding: spacing["1"], 38 43 }, 39 44 sm: { 40 - height: spacing["9"], 45 + minHeight: spacing["9"], 41 46 }, 42 47 md: { 43 - height: spacing["9"], 48 + minHeight: spacing["9"], 44 49 }, 45 50 lg: { 46 - height: spacing["11"], 51 + minHeight: spacing["11"], 47 52 }, 48 53 itemInner: { 49 54 alignItems: "center", 50 55 backgroundColor: { 51 56 default: "transparent", 52 - [":is(:hover > *)"]: slate[4], 57 + [":is(:hover:not([data-disabled]) > *,[data-focused] > *)"]: slate[4], 53 58 [":is(:active > *)"]: slate[5], 54 59 }, 55 60 borderRadius: radius["md"], 56 61 boxSizing: "border-box", 62 + color: { 63 + default: slate[12], 64 + [":is([data-disabled] > *)"]: slate[8], 65 + }, 57 66 display: "flex", 58 67 flexGrow: 1, 59 68 gap: spacing["2"], 60 - justifyContent: "space-between", 69 + paddingBottom: spacing["2"], 61 70 paddingLeft: spacing["2"], 62 71 paddingRight: spacing["2"], 72 + paddingTop: spacing["2"], 63 73 transitionDuration: "100ms", 64 74 transitionProperty: "background-color", 65 75 transitionTimingFunction: "ease-in-out", 76 + }, 77 + smItemInner: { 78 + fontSize: fontSize["xs"], 79 + lineHeight: lineHeight["xs"], 80 + paddingBottom: spacing["1"], 81 + paddingTop: spacing["1"], 66 82 }, 67 83 check: { 68 84 color: plum[9], ··· 90 106 marginBottom: spacing["1.5"], 91 107 marginTop: spacing["1.5"], 92 108 }, 109 + addon: { 110 + alignItems: "center", 111 + display: "flex", 112 + justifyContent: "center", 113 + minWidth: spacing["4"], 114 + 115 + // eslint-disable-next-line @stylexjs/no-legacy-contextual-styles, @stylexjs/valid-styles 116 + ":is(*) svg": { 117 + color: slate[11], 118 + flexShrink: 0, 119 + height: spacing["4"], 120 + pointerEvents: "none", 121 + width: spacing["4"], 122 + }, 123 + }, 124 + label: { 125 + display: "flex", 126 + flexDirection: "column", 127 + flexGrow: 1, 128 + gap: spacing["1.5"], 129 + }, 93 130 }); 94 131 95 132 export interface ListBoxProps<T extends object> ··· 118 155 extends Omit<AriaListBoxItemProps, "style" | "className" | "children"> { 119 156 style?: stylex.StyleXStyles | stylex.StyleXStyles[]; 120 157 children: React.ReactNode; 158 + prefix?: React.ReactNode; 159 + suffix?: React.ReactNode; 121 160 } 122 161 123 - export function ListBoxItem({ style, children, ...props }: ListBoxItemProps) { 162 + export function ListBoxItem({ 163 + style, 164 + children, 165 + prefix, 166 + suffix, 167 + ...props 168 + }: ListBoxItemProps) { 124 169 const size = useContext(SizeContext); 125 170 126 171 return ( 127 172 <AriaListBoxItem 128 173 {...props} 174 + value={props.value || { id: props.id, label: children }} 175 + textValue={ 176 + props.textValue || (typeof children === "string" ? children : undefined) 177 + } 129 178 {...stylex.props(typeramp.label, styles.item, styles[size], style)} 130 179 > 131 180 {({ isSelected }) => ( 132 - <div {...stylex.props(styles.itemInner)}> 133 - {children} 181 + <div 182 + {...stylex.props( 183 + styles.itemInner, 184 + size === "sm" && styles.smItemInner 185 + )} 186 + > 187 + {prefix && <div {...stylex.props(styles.addon)}>{prefix}</div>} 188 + <div {...stylex.props(styles.label)}>{children}</div> 189 + {suffix && <div {...stylex.props(styles.addon)}>{suffix}</div>} 134 190 {isSelected && <Check size={16} {...stylex.props(styles.check)} />} 135 191 </div> 136 192 )}
+6
packages/hip-ui/src/components/select/index.tsx
··· 52 52 children: React.ReactNode | ((item: T) => React.ReactNode); 53 53 size?: Size; 54 54 placeholder?: string; 55 + prefix?: React.ReactNode; 56 + suffix?: React.ReactNode; 55 57 } 56 58 57 59 export function Select< ··· 70 72 shouldUpdatePosition, 71 73 placement, 72 74 placeholder = "Select an option", 75 + prefix, 76 + suffix, 73 77 ...props 74 78 }: SelectProps<T, M>) { 75 79 const inputStyles = useInputStyles({ size }); ··· 84 88 > 85 89 {label && <Label size={size}>{label}</Label>} 86 90 <Button {...stylex.props(inputStyles.wrapper)}> 91 + {prefix && <div {...stylex.props(inputStyles.addon)}>{prefix}</div>} 87 92 <SelectValue {...stylex.props(inputStyles.input)}> 88 93 {({ selectedText, isPlaceholder, defaultChildren }) => { 89 94 if (isPlaceholder) return placeholder; ··· 92 97 return defaultChildren; 93 98 }} 94 99 </SelectValue> 100 + {suffix && <div {...stylex.props(inputStyles.addon)}>{suffix}</div>} 95 101 <div {...stylex.props(inputStyles.addon)}> 96 102 <ChevronDown size={16} aria-hidden="true" /> 97 103 </div>
+1 -1
packages/hip-ui/src/components/theme/useInputStyles.ts
··· 101 101 export function useInputStyles({ size = "md" }: { size?: Size }) { 102 102 return { 103 103 field: [styles.field], 104 - wrapper: [styles.inputWrapper, gray.text, styles[size]], 104 + wrapper: [styles.inputWrapper, gray.bgUi, gray.text, styles[size]], 105 105 input: [styles.input, styles[`${size}Input`]], 106 106 addon: [styles.addon], 107 107 };