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.

lots more fields

+977 -42
+6 -6
README.md
··· 30 30 - [ ] Alert Dialog 31 31 - [ ] Breadcrumb 32 32 - [ ] Calendar 33 - - [ ] Combobox 34 33 - [ ] Color Area 35 - - [ ] Color Field 36 34 - [ ] Color Picker 37 35 - [ ] Color Swatch 38 36 - [ ] Color Swatch Picker 39 37 - [ ] Color Wheel 38 + - [ ] Combobox 40 39 - [ ] Command 41 40 - [ ] Data Table 42 - - [ ] Date Field 43 41 - [ ] Date Picker 44 42 - [ ] Dialog 45 43 - [ ] Disclosure ··· 55 53 - [ ] Meter 56 54 - [ ] Menubar 57 55 - [ ] Navigation Menu 58 - - [ ] Number Field 59 56 - [ ] Progress 60 57 - [ ] Range Date Picker 61 58 - [ ] Segmented Control 62 - - [ ] Search Field 63 59 - [ ] Sheet 64 60 - [ ] Slider 65 61 - [ ] Spinner ··· 67 63 - [ ] Table 68 64 - [ ] Tabs 69 65 - [ ] Tag Group 70 - - [ ] Timefield 71 66 - [ ] Toolbar 72 67 - [ ] Toast 68 + - [x] Number Field 69 + - [x] Color Field 70 + - [x] Search Field 71 + - [x] Date Field 72 + - [x] Timefield 73 73 - [x] Button 74 74 - [x] Button Group 75 75 - [x] Card
+17 -1
apps/example/src/components/KitchenSink.tsx
··· 12 12 ArrowRight, 13 13 ArrowUp, 14 14 Check, 15 + CpuIcon, 15 16 Ellipsis, 16 17 Pin, 17 18 Plus, ··· 64 65 import { ContextMenu } from "./context-menu"; 65 66 import { slate } from "./theme/colors.stylex"; 66 67 import { radius } from "./theme/radius.stylex"; 68 + import { TimeField } from "./time-field"; 69 + import { DateField } from "./date-field"; 70 + import { SearchField } from "./search-field"; 71 + import { ColorField } from "./color-field"; 72 + import { NumberField } from "./number-field"; 67 73 68 74 const styles = stylex.create({ 69 75 subCard: { ··· 315 321 return ( 316 322 <Flex direction="column" gap="4"> 317 323 <TextField label="Text Field" /> 318 - <TextField label="Text Field with Icon" prefix={<Search />} /> 324 + <TextField label="Text Field with Icon" prefix={<CpuIcon />} /> 319 325 <TextField 320 326 label="Text Field with Suffix" 321 327 suffix={<SmallBody variant="secondary">suffix</SmallBody>} 322 328 /> 323 329 <TextField label="Text Field with Icon" suffix={<Check />} /> 330 + <SearchField label="Search Field" /> 331 + <ColorField label="Color Field" /> 332 + <NumberField 333 + label="Number Field" 334 + minValue={0} 335 + prefix={<CpuIcon />} 336 + defaultValue={1} 337 + /> 338 + <TimeField label="Time Field" size="lg" /> 339 + <DateField label="Date Field" size="lg" /> 324 340 </Flex> 325 341 ); 326 342 }
+60
apps/example/src/components/color-field/index.tsx
··· 1 + import { 2 + ColorFieldProps as AriaColorFieldProps, 3 + Input, 4 + InputProps, 5 + ValidationResult, 6 + FieldError, 7 + ColorField as AriaColorField, 8 + } from "react-aria-components"; 9 + import * as stylex from "@stylexjs/stylex"; 10 + import { Description, Label } from "../label"; 11 + import { useRef } from "react"; 12 + import { Size } from "../types"; 13 + import { useInputStyles } from "../theme/useInputStyles"; 14 + 15 + export interface ColorFieldProps 16 + extends Omit<AriaColorFieldProps, "style" | "className">, 17 + Pick<InputProps, "placeholder"> { 18 + style?: stylex.StyleXStyles | stylex.StyleXStyles[]; 19 + label?: React.ReactNode; 20 + description?: string; 21 + errorMessage?: string | ((validation: ValidationResult) => string); 22 + size?: Size; 23 + prefix?: React.ReactNode; 24 + suffix?: React.ReactNode; 25 + } 26 + 27 + export function ColorField({ 28 + label, 29 + description, 30 + errorMessage, 31 + style, 32 + size = "md", 33 + prefix, 34 + suffix, 35 + placeholder, 36 + ...props 37 + }: ColorFieldProps) { 38 + const inputRef = useRef<HTMLInputElement>(null); 39 + const inputStyles = useInputStyles({ size }); 40 + 41 + return ( 42 + <AriaColorField {...props} {...stylex.props(inputStyles.field, style)}> 43 + <Label size={size}>{label}</Label> 44 + <div 45 + {...stylex.props(inputStyles.wrapper)} 46 + onClick={() => inputRef.current?.focus()} 47 + > 48 + {prefix && <div {...stylex.props(inputStyles.addon)}>{prefix}</div>} 49 + <Input 50 + placeholder={placeholder} 51 + ref={inputRef} 52 + {...stylex.props(inputStyles.input)} 53 + /> 54 + {suffix && <div {...stylex.props(inputStyles.addon)}>{suffix}</div>} 55 + </div> 56 + {description && <Description size={size}>{description}</Description>} 57 + <FieldError>{errorMessage}</FieldError> 58 + </AriaColorField> 59 + ); 60 + }
-1
apps/example/src/components/context-menu/index.tsx
··· 22 22 useState, 23 23 } from "react"; 24 24 import { Size } from "../types"; 25 - import { gray } from "../theme/semantic-color.stylex"; 26 25 import { SizeContext } from "../context"; 27 26 import { useMenuTriggerState } from "react-stately"; 28 27 import { AriaButtonProps, useMenuTrigger } from "react-aria";
+57
apps/example/src/components/date-field/index.tsx
··· 1 + import { 2 + DateFieldProps as AriaDateFieldProps, 3 + DateInput, 4 + DateSegment, 5 + DateValue, 6 + ValidationResult, 7 + } from "react-aria-components"; 8 + 9 + import { FieldError, DateField as AriaDateField } from "react-aria-components"; 10 + import * as stylex from "@stylexjs/stylex"; 11 + import { Description, Label } from "../label"; 12 + import { useRef } from "react"; 13 + import { Size } from "../types"; 14 + import { useInputStyles } from "../theme/useInputStyles"; 15 + 16 + export interface DateFieldProps<T extends DateValue> 17 + extends Omit<AriaDateFieldProps<T>, "style" | "className"> { 18 + style?: stylex.StyleXStyles | stylex.StyleXStyles[]; 19 + label?: React.ReactNode; 20 + description?: string; 21 + errorMessage?: string | ((validation: ValidationResult) => string); 22 + size?: Size; 23 + prefix?: React.ReactNode; 24 + suffix?: React.ReactNode; 25 + } 26 + 27 + export function DateField<T extends DateValue>({ 28 + label, 29 + description, 30 + errorMessage, 31 + style, 32 + size = "md", 33 + prefix, 34 + suffix, 35 + ...props 36 + }: DateFieldProps<T>) { 37 + const inputRef = useRef<HTMLInputElement>(null); 38 + const inputStyles = useInputStyles({ size }); 39 + 40 + return ( 41 + <AriaDateField {...props} {...stylex.props(inputStyles.field, style)}> 42 + <Label size={size}>{label}</Label> 43 + <div 44 + {...stylex.props(inputStyles.wrapper)} 45 + onClick={() => inputRef.current?.focus()} 46 + > 47 + {prefix && <div {...stylex.props(inputStyles.addon)}>{prefix}</div>} 48 + <DateInput {...stylex.props(inputStyles.input)} ref={inputRef}> 49 + {(segment) => <DateSegment segment={segment} />} 50 + </DateInput> 51 + {suffix && <div {...stylex.props(inputStyles.addon)}>{suffix}</div>} 52 + </div> 53 + {description && <Description size={size}>{description}</Description>} 54 + <FieldError>{errorMessage}</FieldError> 55 + </AriaDateField> 56 + ); 57 + }
+120
apps/example/src/components/number-field/index.tsx
··· 1 + import { 2 + NumberFieldProps as AriaNumberFieldProps, 3 + Input, 4 + InputProps, 5 + ValidationResult, 6 + FieldError, 7 + NumberField as AriaNumberField, 8 + Group, 9 + Button, 10 + } from "react-aria-components"; 11 + import * as stylex from "@stylexjs/stylex"; 12 + import { Description, Label } from "../label"; 13 + import { useRef } from "react"; 14 + import { Size } from "../types"; 15 + import { useInputStyles } from "../theme/useInputStyles"; 16 + import { Minus, Plus } from "lucide-react"; 17 + import { gray } from "../theme/semantic-color.stylex"; 18 + import { spacing } from "../theme/spacing.stylex"; 19 + import { slate } from "../theme/colors.stylex"; 20 + 21 + const styles = stylex.create({ 22 + buttons: { 23 + display: "flex", 24 + }, 25 + button: { 26 + alignItems: "center", 27 + borderBottomWidth: 0, 28 + borderLeftStyle: "solid", 29 + borderLeftWidth: 1, 30 + borderRightWidth: 0, 31 + borderTopWidth: 0, 32 + borderWidth: 0, 33 + display: "flex", 34 + flexGrow: 1, 35 + justifyContent: "center", 36 + minHeight: 0, 37 + 38 + // eslint-disable-next-line @stylexjs/no-legacy-contextual-styles, @stylexjs/valid-styles 39 + ":is(*) svg": { 40 + flexShrink: 0, 41 + height: spacing["4"], 42 + pointerEvents: "none", 43 + width: spacing["4"], 44 + }, 45 + 46 + color: { 47 + default: slate[12], 48 + ":disabled": slate[11], 49 + }, 50 + }, 51 + }); 52 + 53 + export interface NumberFieldProps 54 + extends Omit<AriaNumberFieldProps, "style" | "className">, 55 + Pick<InputProps, "placeholder"> { 56 + style?: stylex.StyleXStyles | stylex.StyleXStyles[]; 57 + label?: React.ReactNode; 58 + description?: string; 59 + errorMessage?: string | ((validation: ValidationResult) => string); 60 + size?: Size; 61 + prefix?: React.ReactNode; 62 + suffix?: React.ReactNode; 63 + } 64 + 65 + export function NumberField({ 66 + label, 67 + description, 68 + errorMessage, 69 + style, 70 + size = "md", 71 + prefix, 72 + suffix, 73 + placeholder, 74 + ...props 75 + }: NumberFieldProps) { 76 + const inputRef = useRef<HTMLInputElement>(null); 77 + const inputStyles = useInputStyles({ size }); 78 + 79 + return ( 80 + <AriaNumberField {...props} {...stylex.props(inputStyles.field, style)}> 81 + <Label size={size}>{label}</Label> 82 + <div 83 + {...stylex.props(inputStyles.wrapper)} 84 + onClick={() => inputRef.current?.focus()} 85 + > 86 + {prefix && <div {...stylex.props(inputStyles.addon)}>{prefix}</div>} 87 + <Input 88 + placeholder={placeholder} 89 + ref={inputRef} 90 + {...stylex.props(inputStyles.input)} 91 + /> 92 + {suffix && <div {...stylex.props(inputStyles.addon)}>{suffix}</div>} 93 + <Group {...stylex.props(styles.buttons)}> 94 + <Button 95 + slot="decrement" 96 + {...stylex.props( 97 + styles.button, 98 + gray.borderInteractive, 99 + gray.bgAction 100 + )} 101 + > 102 + <Minus /> 103 + </Button> 104 + <Button 105 + slot="increment" 106 + {...stylex.props( 107 + styles.button, 108 + gray.borderInteractive, 109 + gray.bgAction 110 + )} 111 + > 112 + <Plus /> 113 + </Button> 114 + </Group> 115 + </div> 116 + {description && <Description size={size}>{description}</Description>} 117 + <FieldError>{errorMessage}</FieldError> 118 + </AriaNumberField> 119 + ); 120 + }
+104
apps/example/src/components/search-field/index.tsx
··· 1 + import { 2 + SearchFieldProps as AriaSearchFieldProps, 3 + Input, 4 + InputProps, 5 + ValidationResult, 6 + FieldError, 7 + SearchField as AriaSearchField, 8 + } from "react-aria-components"; 9 + import * as stylex from "@stylexjs/stylex"; 10 + import { Description, Label } from "../label"; 11 + import { useRef } from "react"; 12 + import { Size } from "../types"; 13 + import { useInputStyles } from "../theme/useInputStyles"; 14 + import { SearchIcon, X } from "lucide-react"; 15 + import { IconButton } from "../icon-button"; 16 + import { spacing } from "../theme/spacing.stylex"; 17 + 18 + const styles = stylex.create({ 19 + wrapper: { 20 + position: "relative", 21 + }, 22 + clearButton: { 23 + position: "absolute", 24 + right: 0, 25 + top: "50%", 26 + transform: "translateY(-50%)", 27 + }, 28 + clearButtonPadding: { 29 + paddingRight: spacing["8"], 30 + }, 31 + }); 32 + 33 + export interface SearchFieldProps 34 + extends Omit<AriaSearchFieldProps, "style" | "className">, 35 + Pick<InputProps, "placeholder"> { 36 + style?: stylex.StyleXStyles | stylex.StyleXStyles[]; 37 + label?: React.ReactNode; 38 + description?: string; 39 + errorMessage?: string | ((validation: ValidationResult) => string); 40 + size?: Size; 41 + prefix?: React.ReactNode; 42 + suffix?: React.ReactNode; 43 + } 44 + 45 + export function SearchField({ 46 + label, 47 + description, 48 + errorMessage, 49 + style, 50 + size = "md", 51 + prefix = <SearchIcon />, 52 + suffix, 53 + placeholder, 54 + ...props 55 + }: SearchFieldProps) { 56 + const inputRef = useRef<HTMLInputElement>(null); 57 + const inputStyles = useInputStyles({ size }); 58 + 59 + return ( 60 + <AriaSearchField {...props} {...stylex.props(inputStyles.field, style)}> 61 + {({ isEmpty }) => { 62 + return ( 63 + <> 64 + <Label size={size}>{label}</Label> 65 + <div 66 + {...stylex.props(inputStyles.wrapper, styles.wrapper)} 67 + onClick={() => inputRef.current?.focus()} 68 + > 69 + {prefix && ( 70 + <div {...stylex.props(inputStyles.addon)}>{prefix}</div> 71 + )} 72 + <Input 73 + placeholder={placeholder} 74 + ref={inputRef} 75 + {...stylex.props( 76 + inputStyles.input, 77 + !isEmpty && styles.clearButtonPadding 78 + )} 79 + /> 80 + {suffix && ( 81 + <div {...stylex.props(inputStyles.addon)}>{suffix}</div> 82 + )} 83 + {!isEmpty && ( 84 + <div {...stylex.props(inputStyles.addon, styles.clearButton)}> 85 + <IconButton 86 + label="Clear search" 87 + size="sm" 88 + variant="secondary" 89 + > 90 + <X /> 91 + </IconButton> 92 + </div> 93 + )} 94 + </div> 95 + {description && ( 96 + <Description size={size}>{description}</Description> 97 + )} 98 + <FieldError>{errorMessage}</FieldError> 99 + </> 100 + ); 101 + }} 102 + </AriaSearchField> 103 + ); 104 + }
+21 -15
apps/example/src/components/theme/semantic-color.stylex.tsx
··· 21 21 bgUi: { 22 22 backgroundColor: { 23 23 default: slate[3], 24 - ":hover": slate[4], 25 - ":active": slate[5], 24 + ":hover:not(:has(* button:hover)):not(:disabled)": slate[4], 25 + ":active:not(:disabled)": slate[5], 26 + ":disabled": slate[3], 26 27 }, 27 28 transitionDuration: "100ms", 28 - transitionProperty: "background-color", 29 + transitionProperty: "background-color, border-color", 29 30 transitionTimingFunction: "ease-in-out", 30 31 }, 31 32 bgGhost: { 32 33 backgroundColor: { 33 34 default: "transparent", 34 - ":hover": slate[4], 35 - ":active": slate[5], 35 + ":hover:not(:has(* button:hover)):not(:disabled)": slate[4], 36 + ":active:not(:disabled)": slate[5], 37 + ":disabled": slate[3], 36 38 }, 37 39 transitionDuration: "100ms", 38 - transitionProperty: "background-color", 40 + transitionProperty: "background-color, border-color", 39 41 transitionTimingFunction: "ease-in-out", 40 42 }, 41 43 bgAction: { 42 44 backgroundColor: { 43 45 default: slate[4], 44 - ":hover": slate[5], 45 - ":active": slate[6], 46 + ":hover:not(:has(* button:hover)):not(:disabled)": slate[5], 47 + ":active:not(:disabled)": slate[6], 48 + ":disabled": slate[3], 46 49 }, 47 50 transitionDuration: "100ms", 48 - transitionProperty: "background-color", 51 + transitionProperty: "background-color, border-color", 49 52 transitionTimingFunction: "ease-in-out", 50 53 }, 51 54 borderDim: { ··· 67 70 borderWidth: 1, 68 71 69 72 transitionDuration: "100ms", 70 - transitionProperty: "border-color", 73 + transitionProperty: "background-color, border-color", 71 74 transitionTimingFunction: "ease-in-out", 72 75 }, 73 76 bgSolid: { ··· 104 107 bgUi: { 105 108 backgroundColor: { 106 109 default: plum[3], 107 - ":hover": plum[4], 110 + ":hover:not(:has(* button:hover)):not(:disabled)": plum[4], 108 111 ":active": plum[5], 112 + ":disabled": plum[3], 109 113 }, 110 114 }, 111 115 bgGhost: { 112 116 backgroundColor: { 113 117 default: "transparent", 114 - ":hover": plum[4], 115 - ":active": plum[5], 118 + ":hover:not(:has(* button:hover)):not(:disabled)": plum[4], 119 + ":active:not(:disabled)": plum[5], 120 + ":disabled": plum[3], 116 121 }, 117 122 }, 118 123 bgAction: { 119 124 backgroundColor: { 120 125 default: plum[4], 121 - ":hover": plum[5], 122 - ":active": plum[6], 126 + ":hover:not(:has(* button:hover)):not(:disabled)": plum[5], 127 + ":active:not(:disabled)": plum[6], 128 + ":disabled": plum[3], 123 129 }, 124 130 }, 125 131 borderDim: {
+10 -2
apps/example/src/components/theme/useInputStyles.ts
··· 40 40 borderRadius: radius["md"], 41 41 boxSizing: "border-box", 42 42 display: "flex", 43 - gap: spacing["2"], 44 43 lineHeight: lineHeight["none"], 44 + overflow: "hidden", 45 45 padding: 0, 46 46 47 47 borderColor: { ··· 51 51 }, 52 52 borderStyle: "solid", 53 53 borderWidth: 1, 54 + 55 + transitionProperty: "background-color, border-color", 54 56 }, 55 57 input: { 56 58 alignItems: "center", 57 59 backgroundColor: "transparent", 58 60 borderWidth: 0, 61 + boxSizing: "border-box", 59 62 color: { 60 63 ":is(::placeholder,[data-placeholder])": slate[11], 61 64 }, ··· 63 66 flexGrow: 1, 64 67 lineHeight: lineHeight["none"], 65 68 outline: "none", 69 + 70 + appearance: { 71 + "::-webkit-search-cancel-button": "none", 72 + "::-webkit-search-decoration": "none", 73 + }, 66 74 }, 67 75 sm: { 68 76 height: spacing["6"], ··· 93 101 export function useInputStyles({ size = "md" }: { size?: Size }) { 94 102 return { 95 103 field: [styles.field], 96 - wrapper: [styles.inputWrapper, gray.bgUi, gray.text, styles[size]], 104 + wrapper: [styles.inputWrapper, gray.text, styles[size]], 97 105 input: [styles.input, styles[`${size}Input`]], 98 106 addon: [styles.addon], 99 107 };
+57
apps/example/src/components/time-field/index.tsx
··· 1 + import { 2 + TimeFieldProps as AriaTimeFieldProps, 3 + DateInput, 4 + DateSegment, 5 + TimeValue, 6 + ValidationResult, 7 + } from "react-aria-components"; 8 + 9 + import { FieldError, TimeField as AriaTimeField } from "react-aria-components"; 10 + import * as stylex from "@stylexjs/stylex"; 11 + import { Description, Label } from "../label"; 12 + import { useRef } from "react"; 13 + import { Size } from "../types"; 14 + import { useInputStyles } from "../theme/useInputStyles"; 15 + 16 + export interface TimeFieldProps<T extends TimeValue> 17 + extends Omit<AriaTimeFieldProps<T>, "style" | "className"> { 18 + style?: stylex.StyleXStyles | stylex.StyleXStyles[]; 19 + label?: React.ReactNode; 20 + description?: string; 21 + errorMessage?: string | ((validation: ValidationResult) => string); 22 + size?: Size; 23 + prefix?: React.ReactNode; 24 + suffix?: React.ReactNode; 25 + } 26 + 27 + export function TimeField<T extends TimeValue>({ 28 + label, 29 + description, 30 + errorMessage, 31 + style, 32 + size = "md", 33 + prefix, 34 + suffix, 35 + ...props 36 + }: TimeFieldProps<T>) { 37 + const inputRef = useRef<HTMLInputElement>(null); 38 + const inputStyles = useInputStyles({ size }); 39 + 40 + return ( 41 + <AriaTimeField {...props} {...stylex.props(inputStyles.field, style)}> 42 + <Label size={size}>{label}</Label> 43 + <div 44 + {...stylex.props(inputStyles.wrapper)} 45 + onClick={() => inputRef.current?.focus()} 46 + > 47 + {prefix && <div {...stylex.props(inputStyles.addon)}>{prefix}</div>} 48 + <DateInput {...stylex.props(inputStyles.input)} ref={inputRef}> 49 + {(segment) => <DateSegment segment={segment} />} 50 + </DateInput> 51 + {suffix && <div {...stylex.props(inputStyles.addon)}>{suffix}</div>} 52 + </div> 53 + {description && <Description size={size}>{description}</Description>} 54 + <FieldError>{errorMessage}</FieldError> 55 + </AriaTimeField> 56 + ); 57 + }
+10
packages/hip-ui/src/cli/install.tsx
··· 30 30 import { listboxConfig } from "../components/listbox/listbox-config.js"; 31 31 import { menuConfig } from "../components/menu/menu-config.js"; 32 32 import { contextMenuConfig } from "../components/context-menu/context-menu-config.js"; 33 + import { timeFieldConfig } from "../components/time-field/time-field-config.js"; 34 + import { dateFieldConfig } from "../components/date-field/date-field-config.js"; 35 + import { searchFieldConfig } from "../components/search-field/search-field-config.js"; 36 + import { colorFieldConfig } from "../components/color-field/color-field-config.js"; 37 + import { numberFieldConfig } from "../components/number-field/number-field-config.js"; 33 38 34 39 const __dirname = path.dirname(new URL(import.meta.url).pathname); 35 40 ··· 55 60 menuConfig, 56 61 listboxConfig, 57 62 contextMenuConfig, 63 + timeFieldConfig, 64 + dateFieldConfig, 65 + searchFieldConfig, 66 + colorFieldConfig, 67 + numberFieldConfig, 58 68 ]; 59 69 60 70 function StringSetting({
+19
packages/hip-ui/src/components/color-field/color-field-config.ts
··· 1 + import { ComponentConfig } from "../../types"; 2 + 3 + export const colorFieldConfig: ComponentConfig = { 4 + name: "color-field", 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 + ], 16 + dependencies: { 17 + "lucide-react": "^0.545.0", 18 + }, 19 + };
+60
packages/hip-ui/src/components/color-field/index.tsx
··· 1 + import { 2 + ColorFieldProps as AriaColorFieldProps, 3 + Input, 4 + InputProps, 5 + ValidationResult, 6 + FieldError, 7 + ColorField as AriaColorField, 8 + } from "react-aria-components"; 9 + import * as stylex from "@stylexjs/stylex"; 10 + import { Description, Label } from "../label"; 11 + import { useRef } from "react"; 12 + import { Size } from "../types"; 13 + import { useInputStyles } from "../theme/useInputStyles"; 14 + 15 + export interface ColorFieldProps 16 + extends Omit<AriaColorFieldProps, "style" | "className">, 17 + Pick<InputProps, "placeholder"> { 18 + style?: stylex.StyleXStyles | stylex.StyleXStyles[]; 19 + label?: React.ReactNode; 20 + description?: string; 21 + errorMessage?: string | ((validation: ValidationResult) => string); 22 + size?: Size; 23 + prefix?: React.ReactNode; 24 + suffix?: React.ReactNode; 25 + } 26 + 27 + export function ColorField({ 28 + label, 29 + description, 30 + errorMessage, 31 + style, 32 + size = "md", 33 + prefix, 34 + suffix, 35 + placeholder, 36 + ...props 37 + }: ColorFieldProps) { 38 + const inputRef = useRef<HTMLInputElement>(null); 39 + const inputStyles = useInputStyles({ size }); 40 + 41 + return ( 42 + <AriaColorField {...props} {...stylex.props(inputStyles.field, style)}> 43 + <Label size={size}>{label}</Label> 44 + <div 45 + {...stylex.props(inputStyles.wrapper)} 46 + onClick={() => inputRef.current?.focus()} 47 + > 48 + {prefix && <div {...stylex.props(inputStyles.addon)}>{prefix}</div>} 49 + <Input 50 + placeholder={placeholder} 51 + ref={inputRef} 52 + {...stylex.props(inputStyles.input)} 53 + /> 54 + {suffix && <div {...stylex.props(inputStyles.addon)}>{suffix}</div>} 55 + </div> 56 + {description && <Description size={size}>{description}</Description>} 57 + <FieldError>{errorMessage}</FieldError> 58 + </AriaColorField> 59 + ); 60 + }
+19
packages/hip-ui/src/components/date-field/date-field-config.ts
··· 1 + import { ComponentConfig } from "../../types"; 2 + 3 + export const dateFieldConfig: ComponentConfig = { 4 + name: "date-field", 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 + ], 16 + dependencies: { 17 + "lucide-react": "^0.545.0", 18 + }, 19 + };
+57
packages/hip-ui/src/components/date-field/index.tsx
··· 1 + import { 2 + DateFieldProps as AriaDateFieldProps, 3 + DateInput, 4 + DateSegment, 5 + DateValue, 6 + ValidationResult, 7 + } from "react-aria-components"; 8 + 9 + import { FieldError, DateField as AriaDateField } from "react-aria-components"; 10 + import * as stylex from "@stylexjs/stylex"; 11 + import { Description, Label } from "../label"; 12 + import { useRef } from "react"; 13 + import { Size } from "../types"; 14 + import { useInputStyles } from "../theme/useInputStyles"; 15 + 16 + export interface DateFieldProps<T extends DateValue> 17 + extends Omit<AriaDateFieldProps<T>, "style" | "className"> { 18 + style?: stylex.StyleXStyles | stylex.StyleXStyles[]; 19 + label?: React.ReactNode; 20 + description?: string; 21 + errorMessage?: string | ((validation: ValidationResult) => string); 22 + size?: Size; 23 + prefix?: React.ReactNode; 24 + suffix?: React.ReactNode; 25 + } 26 + 27 + export function DateField<T extends DateValue>({ 28 + label, 29 + description, 30 + errorMessage, 31 + style, 32 + size = "md", 33 + prefix, 34 + suffix, 35 + ...props 36 + }: DateFieldProps<T>) { 37 + const inputRef = useRef<HTMLInputElement>(null); 38 + const inputStyles = useInputStyles({ size }); 39 + 40 + return ( 41 + <AriaDateField {...props} {...stylex.props(inputStyles.field, style)}> 42 + <Label size={size}>{label}</Label> 43 + <div 44 + {...stylex.props(inputStyles.wrapper)} 45 + onClick={() => inputRef.current?.focus()} 46 + > 47 + {prefix && <div {...stylex.props(inputStyles.addon)}>{prefix}</div>} 48 + <DateInput {...stylex.props(inputStyles.input)} ref={inputRef}> 49 + {(segment) => <DateSegment segment={segment} />} 50 + </DateInput> 51 + {suffix && <div {...stylex.props(inputStyles.addon)}>{suffix}</div>} 52 + </div> 53 + {description && <Description size={size}>{description}</Description>} 54 + <FieldError>{errorMessage}</FieldError> 55 + </AriaDateField> 56 + ); 57 + }
+111
packages/hip-ui/src/components/number-field/index.tsx
··· 1 + import { 2 + NumberFieldProps as AriaNumberFieldProps, 3 + Input, 4 + InputProps, 5 + ValidationResult, 6 + FieldError, 7 + NumberField as AriaNumberField, 8 + Group, 9 + Button, 10 + } from "react-aria-components"; 11 + import * as stylex from "@stylexjs/stylex"; 12 + import { Description, Label } from "../label"; 13 + import { useRef } from "react"; 14 + import { Size } from "../types"; 15 + import { useInputStyles } from "../theme/useInputStyles"; 16 + import { Minus, Plus } from "lucide-react"; 17 + import { gray } from "../theme/semantic-color.stylex"; 18 + import { spacing } from "../theme/spacing.stylex"; 19 + import { slate } from "../theme/colors.stylex"; 20 + 21 + const styles = stylex.create({ 22 + buttons: { 23 + display: "flex", 24 + }, 25 + button: { 26 + alignItems: "center", 27 + borderBottomWidth: 0, 28 + borderLeftStyle: "solid", 29 + borderLeftWidth: 1, 30 + borderRightWidth: 0, 31 + borderTopWidth: 0, 32 + borderWidth: 0, 33 + display: "flex", 34 + flexGrow: 1, 35 + justifyContent: "center", 36 + minHeight: 0, 37 + 38 + // eslint-disable-next-line @stylexjs/no-legacy-contextual-styles, @stylexjs/valid-styles 39 + ":is(*) svg": { 40 + flexShrink: 0, 41 + height: spacing["4"], 42 + pointerEvents: "none", 43 + width: spacing["4"], 44 + }, 45 + 46 + color: { 47 + default: slate[12], 48 + ":disabled": slate[11], 49 + }, 50 + }, 51 + }); 52 + 53 + export interface NumberFieldProps 54 + extends Omit<AriaNumberFieldProps, "style" | "className">, 55 + Pick<InputProps, "placeholder"> { 56 + style?: stylex.StyleXStyles | stylex.StyleXStyles[]; 57 + label?: React.ReactNode; 58 + description?: string; 59 + errorMessage?: string | ((validation: ValidationResult) => string); 60 + size?: Size; 61 + prefix?: React.ReactNode; 62 + suffix?: React.ReactNode; 63 + } 64 + 65 + export function NumberField({ 66 + label, 67 + description, 68 + errorMessage, 69 + style, 70 + size = "md", 71 + prefix, 72 + suffix, 73 + placeholder, 74 + ...props 75 + }: NumberFieldProps) { 76 + const inputRef = useRef<HTMLInputElement>(null); 77 + const inputStyles = useInputStyles({ size }); 78 + const buttonStyles = stylex.props( 79 + styles.button, 80 + gray.borderInteractive, 81 + gray.bgAction 82 + ); 83 + 84 + return ( 85 + <AriaNumberField {...props} {...stylex.props(inputStyles.field, style)}> 86 + <Label size={size}>{label}</Label> 87 + <div 88 + {...stylex.props(inputStyles.wrapper)} 89 + onClick={() => inputRef.current?.focus()} 90 + > 91 + {prefix && <div {...stylex.props(inputStyles.addon)}>{prefix}</div>} 92 + <Input 93 + placeholder={placeholder} 94 + ref={inputRef} 95 + {...stylex.props(inputStyles.input)} 96 + /> 97 + {suffix && <div {...stylex.props(inputStyles.addon)}>{suffix}</div>} 98 + <Group {...stylex.props(styles.buttons)}> 99 + <Button slot="decrement" {...buttonStyles}> 100 + <Minus /> 101 + </Button> 102 + <Button slot="increment" {...buttonStyles}> 103 + <Plus /> 104 + </Button> 105 + </Group> 106 + </div> 107 + {description && <Description size={size}>{description}</Description>} 108 + <FieldError>{errorMessage}</FieldError> 109 + </AriaNumberField> 110 + ); 111 + }
+19
packages/hip-ui/src/components/number-field/number-field-config.ts
··· 1 + import { ComponentConfig } from "../../types"; 2 + 3 + export const numberFieldConfig: ComponentConfig = { 4 + name: "number-field", 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 + ], 16 + dependencies: { 17 + "lucide-react": "^0.545.0", 18 + }, 19 + };
+104
packages/hip-ui/src/components/search-field/index.tsx
··· 1 + import { 2 + SearchFieldProps as AriaSearchFieldProps, 3 + Input, 4 + InputProps, 5 + ValidationResult, 6 + FieldError, 7 + SearchField as AriaSearchField, 8 + } from "react-aria-components"; 9 + import * as stylex from "@stylexjs/stylex"; 10 + import { Description, Label } from "../label"; 11 + import { useRef } from "react"; 12 + import { Size } from "../types"; 13 + import { useInputStyles } from "../theme/useInputStyles"; 14 + import { SearchIcon, X } from "lucide-react"; 15 + import { IconButton } from "../icon-button"; 16 + import { spacing } from "../theme/spacing.stylex"; 17 + 18 + const styles = stylex.create({ 19 + wrapper: { 20 + position: "relative", 21 + }, 22 + clearButton: { 23 + position: "absolute", 24 + right: 0, 25 + top: "50%", 26 + transform: "translateY(-50%)", 27 + }, 28 + clearButtonPadding: { 29 + paddingRight: spacing["8"], 30 + }, 31 + }); 32 + 33 + export interface SearchFieldProps 34 + extends Omit<AriaSearchFieldProps, "style" | "className">, 35 + Pick<InputProps, "placeholder"> { 36 + style?: stylex.StyleXStyles | stylex.StyleXStyles[]; 37 + label?: React.ReactNode; 38 + description?: string; 39 + errorMessage?: string | ((validation: ValidationResult) => string); 40 + size?: Size; 41 + prefix?: React.ReactNode; 42 + suffix?: React.ReactNode; 43 + } 44 + 45 + export function SearchField({ 46 + label, 47 + description, 48 + errorMessage, 49 + style, 50 + size = "md", 51 + prefix = <SearchIcon />, 52 + suffix, 53 + placeholder, 54 + ...props 55 + }: SearchFieldProps) { 56 + const inputRef = useRef<HTMLInputElement>(null); 57 + const inputStyles = useInputStyles({ size }); 58 + 59 + return ( 60 + <AriaSearchField {...props} {...stylex.props(inputStyles.field, style)}> 61 + {({ isEmpty }) => { 62 + return ( 63 + <> 64 + <Label size={size}>{label}</Label> 65 + <div 66 + {...stylex.props(inputStyles.wrapper, styles.wrapper)} 67 + onClick={() => inputRef.current?.focus()} 68 + > 69 + {prefix && ( 70 + <div {...stylex.props(inputStyles.addon)}>{prefix}</div> 71 + )} 72 + <Input 73 + placeholder={placeholder} 74 + ref={inputRef} 75 + {...stylex.props( 76 + inputStyles.input, 77 + !isEmpty && styles.clearButtonPadding 78 + )} 79 + /> 80 + {suffix && ( 81 + <div {...stylex.props(inputStyles.addon)}>{suffix}</div> 82 + )} 83 + {!isEmpty && ( 84 + <div {...stylex.props(inputStyles.addon, styles.clearButton)}> 85 + <IconButton 86 + label="Clear search" 87 + size="sm" 88 + variant="secondary" 89 + > 90 + <X /> 91 + </IconButton> 92 + </div> 93 + )} 94 + </div> 95 + {description && ( 96 + <Description size={size}>{description}</Description> 97 + )} 98 + <FieldError>{errorMessage}</FieldError> 99 + </> 100 + ); 101 + }} 102 + </AriaSearchField> 103 + ); 104 + }
+19
packages/hip-ui/src/components/search-field/search-field-config.ts
··· 1 + import { ComponentConfig } from "../../types"; 2 + 3 + export const searchFieldConfig: ComponentConfig = { 4 + name: "search-field", 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 + ], 16 + dependencies: { 17 + "lucide-react": "^0.545.0", 18 + }, 19 + };
+21 -15
packages/hip-ui/src/components/theme/semantic-color.stylex.tsx
··· 21 21 bgUi: { 22 22 backgroundColor: { 23 23 default: slate[3], 24 - ":hover": slate[4], 25 - ":active": slate[5], 24 + ":hover:not(:has(* button:hover)):not(:disabled)": slate[4], 25 + ":active:not(:disabled)": slate[5], 26 + ":disabled": slate[3], 26 27 }, 27 28 transitionDuration: "100ms", 28 - transitionProperty: "background-color", 29 + transitionProperty: "background-color, border-color", 29 30 transitionTimingFunction: "ease-in-out", 30 31 }, 31 32 bgGhost: { 32 33 backgroundColor: { 33 34 default: "transparent", 34 - ":hover": slate[4], 35 - ":active": slate[5], 35 + ":hover:not(:has(* button:hover)):not(:disabled)": slate[4], 36 + ":active:not(:disabled)": slate[5], 37 + ":disabled": slate[3], 36 38 }, 37 39 transitionDuration: "100ms", 38 - transitionProperty: "background-color", 40 + transitionProperty: "background-color, border-color", 39 41 transitionTimingFunction: "ease-in-out", 40 42 }, 41 43 bgAction: { 42 44 backgroundColor: { 43 45 default: slate[4], 44 - ":hover": slate[5], 45 - ":active": slate[6], 46 + ":hover:not(:has(* button:hover)):not(:disabled)": slate[5], 47 + ":active:not(:disabled)": slate[6], 48 + ":disabled": slate[3], 46 49 }, 47 50 transitionDuration: "100ms", 48 - transitionProperty: "background-color", 51 + transitionProperty: "background-color, border-color", 49 52 transitionTimingFunction: "ease-in-out", 50 53 }, 51 54 borderDim: { ··· 67 70 borderWidth: 1, 68 71 69 72 transitionDuration: "100ms", 70 - transitionProperty: "border-color", 73 + transitionProperty: "background-color, border-color", 71 74 transitionTimingFunction: "ease-in-out", 72 75 }, 73 76 bgSolid: { ··· 104 107 bgUi: { 105 108 backgroundColor: { 106 109 default: plum[3], 107 - ":hover": plum[4], 110 + ":hover:not(:has(* button:hover)):not(:disabled)": plum[4], 108 111 ":active": plum[5], 112 + ":disabled": plum[3], 109 113 }, 110 114 }, 111 115 bgGhost: { 112 116 backgroundColor: { 113 117 default: "transparent", 114 - ":hover": plum[4], 115 - ":active": plum[5], 118 + ":hover:not(:has(* button:hover)):not(:disabled)": plum[4], 119 + ":active:not(:disabled)": plum[5], 120 + ":disabled": plum[3], 116 121 }, 117 122 }, 118 123 bgAction: { 119 124 backgroundColor: { 120 125 default: plum[4], 121 - ":hover": plum[5], 122 - ":active": plum[6], 126 + ":hover:not(:has(* button:hover)):not(:disabled)": plum[5], 127 + ":active:not(:disabled)": plum[6], 128 + ":disabled": plum[3], 123 129 }, 124 130 }, 125 131 borderDim: {
+10 -2
packages/hip-ui/src/components/theme/useInputStyles.ts
··· 40 40 borderRadius: radius["md"], 41 41 boxSizing: "border-box", 42 42 display: "flex", 43 - gap: spacing["2"], 44 43 lineHeight: lineHeight["none"], 44 + overflow: "hidden", 45 45 padding: 0, 46 46 47 47 borderColor: { ··· 51 51 }, 52 52 borderStyle: "solid", 53 53 borderWidth: 1, 54 + 55 + transitionProperty: "background-color, border-color", 54 56 }, 55 57 input: { 56 58 alignItems: "center", 57 59 backgroundColor: "transparent", 58 60 borderWidth: 0, 61 + boxSizing: "border-box", 59 62 color: { 60 63 ":is(::placeholder,[data-placeholder])": slate[11], 61 64 }, ··· 63 66 flexGrow: 1, 64 67 lineHeight: lineHeight["none"], 65 68 outline: "none", 69 + 70 + appearance: { 71 + "::-webkit-search-cancel-button": "none", 72 + "::-webkit-search-decoration": "none", 73 + }, 66 74 }, 67 75 sm: { 68 76 height: spacing["6"], ··· 93 101 export function useInputStyles({ size = "md" }: { size?: Size }) { 94 102 return { 95 103 field: [styles.field], 96 - wrapper: [styles.inputWrapper, gray.bgUi, gray.text, styles[size]], 104 + wrapper: [styles.inputWrapper, gray.text, styles[size]], 97 105 input: [styles.input, styles[`${size}Input`]], 98 106 addon: [styles.addon], 99 107 };
+57
packages/hip-ui/src/components/time-field/index.tsx
··· 1 + import { 2 + TimeFieldProps as AriaTimeFieldProps, 3 + DateInput, 4 + DateSegment, 5 + TimeValue, 6 + ValidationResult, 7 + } from "react-aria-components"; 8 + 9 + import { FieldError, TimeField as AriaTimeField } from "react-aria-components"; 10 + import * as stylex from "@stylexjs/stylex"; 11 + import { Description, Label } from "../label"; 12 + import { useRef } from "react"; 13 + import { Size } from "../types"; 14 + import { useInputStyles } from "../theme/useInputStyles"; 15 + 16 + export interface TimeFieldProps<T extends TimeValue> 17 + extends Omit<AriaTimeFieldProps<T>, "style" | "className"> { 18 + style?: stylex.StyleXStyles | stylex.StyleXStyles[]; 19 + label?: React.ReactNode; 20 + description?: string; 21 + errorMessage?: string | ((validation: ValidationResult) => string); 22 + size?: Size; 23 + prefix?: React.ReactNode; 24 + suffix?: React.ReactNode; 25 + } 26 + 27 + export function TimeField<T extends TimeValue>({ 28 + label, 29 + description, 30 + errorMessage, 31 + style, 32 + size = "md", 33 + prefix, 34 + suffix, 35 + ...props 36 + }: TimeFieldProps<T>) { 37 + const inputRef = useRef<HTMLInputElement>(null); 38 + const inputStyles = useInputStyles({ size }); 39 + 40 + return ( 41 + <AriaTimeField {...props} {...stylex.props(inputStyles.field, style)}> 42 + <Label size={size}>{label}</Label> 43 + <div 44 + {...stylex.props(inputStyles.wrapper)} 45 + onClick={() => inputRef.current?.focus()} 46 + > 47 + {prefix && <div {...stylex.props(inputStyles.addon)}>{prefix}</div>} 48 + <DateInput {...stylex.props(inputStyles.input)} ref={inputRef}> 49 + {(segment) => <DateSegment segment={segment} />} 50 + </DateInput> 51 + {suffix && <div {...stylex.props(inputStyles.addon)}>{suffix}</div>} 52 + </div> 53 + {description && <Description size={size}>{description}</Description>} 54 + <FieldError>{errorMessage}</FieldError> 55 + </AriaTimeField> 56 + ); 57 + }
+19
packages/hip-ui/src/components/time-field/time-field-config.ts
··· 1 + import { ComponentConfig } from "../../types"; 2 + 3 + export const timeFieldConfig: ComponentConfig = { 4 + name: "time-field", 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 + ], 16 + dependencies: { 17 + "lucide-react": "^0.545.0", 18 + }, 19 + };