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.

tweaks

+357 -219
+2 -2
packages/hip-ui/src/components/aspect-ratio/index.tsx
··· 1 1 import * as stylex from "@stylexjs/stylex"; 2 2 3 + import type { StyleXComponentProps } from "../theme/types"; 4 + 3 5 import { mediaQueries } from "../theme/media-queries.stylex"; 4 6 import { radius } from "../theme/radius.stylex"; 5 - import { StyleXComponentProps } from "../theme/types"; 6 7 7 8 const styles = stylex.create({ 8 9 aspectRatio: (aspectRatio: number) => ({ ··· 13 14 position: "relative", 14 15 }, 15 16 rounded: { 16 - // eslint-disable-next-line @stylexjs/valid-styles 17 17 cornerShape: "squircle", 18 18 borderBottomLeftRadius: { 19 19 default: radius["md"],
+54 -9
packages/hip-ui/src/components/button/index.tsx
··· 1 1 "use client"; 2 2 3 + import type { ButtonProps as AriaButtonProps } from "react-aria-components"; 4 + 3 5 import * as stylex from "@stylexjs/stylex"; 4 - import { 5 - Button as AriaButton, 6 - ButtonProps as AriaButtonProps, 7 - } from "react-aria-components"; 6 + import { Button as AriaButton } from "react-aria-components"; 8 7 9 - import { Size, ButtonVariant, StyleXComponentProps } from "../theme/types"; 8 + import type { ButtonVariant, Size, StyleXComponentProps } from "../theme/types"; 9 + 10 + import { ProgressCircle } from "../progress-circle"; 11 + import { animationDuration } from "../theme/animations.stylex"; 12 + import { spacing } from "../theme/spacing.stylex"; 10 13 import { useButtonStyles } from "../theme/useButtonStyles"; 11 14 12 - export interface ButtonProps extends StyleXComponentProps<AriaButtonProps> { 15 + const styles = stylex.create({ 16 + content: { 17 + gap: spacing["2"], 18 + alignItems: "center", 19 + display: "flex", 20 + justifyContent: "center", 21 + transitionDuration: animationDuration.fast, 22 + transitionProperty: "opacity", 23 + transitionTimingFunction: "ease-in-out", 24 + }, 25 + contentPending: { 26 + opacity: 0, 27 + }, 28 + spinner: { 29 + position: "absolute", 30 + }, 31 + link: { 32 + cursor: "pointer", 33 + }, 34 + }); 35 + 36 + export interface ButtonProps extends StyleXComponentProps< 37 + Omit<AriaButtonProps, "children"> 38 + > { 13 39 variant?: ButtonVariant; 14 - size?: Size; 40 + size?: Size | "xl"; 41 + isPending?: boolean; 42 + children?: React.ReactNode; 15 43 } 16 44 17 45 export const Button = ({ ··· 19 47 style, 20 48 variant = "primary", 21 49 size, 50 + isPending = false, 51 + isDisabled, 22 52 ...props 23 53 }: ButtonProps) => { 24 54 const buttonStyles = useButtonStyles({ variant, size }); 55 + const isHref = "href" in props; 25 56 26 57 return ( 27 58 <AriaButton 28 59 {...props} 29 - {...stylex.props(buttonStyles, style)} 60 + {...stylex.props(buttonStyles, isHref && styles.link, style)} 30 61 data-size={size} 62 + data-pending={isPending || undefined} 63 + isDisabled={isDisabled || isPending} 31 64 > 32 - {children} 65 + {isPending && ( 66 + <ProgressCircle 67 + isIndeterminate 68 + size="sm" 69 + style={styles.spinner} 70 + aria-label="Loading" 71 + /> 72 + )} 73 + <span 74 + {...stylex.props(styles.content, isPending && styles.contentPending)} 75 + > 76 + {children} 77 + </span> 33 78 </AriaButton> 34 79 ); 35 80 };
+21 -5
packages/hip-ui/src/components/card/index.tsx
··· 20 20 21 21 const styles = stylex.create({ 22 22 card: { 23 - // eslint-disable-next-line @stylexjs/valid-styles 24 - cornerShape: "squircle", 25 23 borderColor: uiColor.component2, 26 24 borderRadius: { 27 25 default: radius["lg"], ··· 29 27 }, 30 28 borderStyle: "solid", 31 29 borderWidth: 1, 30 + 31 + cornerShape: "squircle", 32 32 gap: "var(--card-gap)", 33 33 overflow: "hidden", 34 34 boxShadow: shadow["sm"], ··· 71 71 alignItems: "center", 72 72 display: "grid", 73 73 }, 74 + headerBorder: { 75 + borderColor: uiColor.component2, 76 + borderBottomStyle: "solid", 77 + borderBottomWidth: 1, 78 + paddingBottom: spacing["6"], 79 + }, 74 80 cardHeaderAction: { 75 81 gridArea: "action", 76 82 gap: spacing["1"], ··· 79 85 }, 80 86 cardTitle: { 81 87 gridArea: "title", 88 + gap: spacing["3"], 89 + alignItems: "center", 90 + display: "flex", 82 91 fontSize: { 83 92 ":is([data-card-size='lg'] *)": fontSize["2xl"], 84 93 ":is([data-card-size='md'] *)": fontSize["xl"], ··· 134 143 135 144 export interface CardHeaderProps extends StyleXComponentProps< 136 145 React.ComponentProps<"div"> 137 - > {} 146 + > { 147 + hasBorder?: boolean; 148 + } 138 149 139 - export const CardHeader = ({ style, ...props }: CardHeaderProps) => { 150 + export const CardHeader = ({ style, hasBorder, ...props }: CardHeaderProps) => { 140 151 return ( 141 152 <div 142 153 {...props} 143 - {...stylex.props(styles.cardSection, styles.cardHeader, style)} 154 + {...stylex.props( 155 + styles.cardSection, 156 + styles.cardHeader, 157 + hasBorder && styles.headerBorder, 158 + style, 159 + )} 144 160 /> 145 161 ); 146 162 };
+24 -5
packages/hip-ui/src/components/hover-card/index.tsx
··· 1 1 "use client"; 2 2 3 + import type { 4 + PopoverProps as AriaPopoverProps, 5 + DialogTriggerProps, 6 + } from "react-aria-components"; 7 + 3 8 import * as stylex from "@stylexjs/stylex"; 4 9 import { use, useRef } from "react"; 5 10 import { mergeProps, useFocusVisible, useHover, useKeyboard } from "react-aria"; 6 11 import { 7 12 Popover as AriaPopover, 8 - PopoverProps as AriaPopoverProps, 13 + Dialog, 9 14 DialogTrigger, 10 - DialogTriggerProps, 11 - Dialog, 15 + OverlayTriggerStateContext, 12 16 Pressable, 13 - OverlayTriggerStateContext, 14 17 } from "react-aria-components"; 15 18 19 + import type { StyleXComponentProps } from "../theme/types"; 20 + 16 21 import { shadow } from "../theme/shadow.stylex"; 17 22 import { spacing } from "../theme/spacing.stylex"; 18 - import { StyleXComponentProps } from "../theme/types"; 19 23 import { usePopoverStyles } from "../theme/usePopoverStyles"; 20 24 21 25 const styles = stylex.create({ ··· 23 27 shadow: shadow.md, 24 28 }, 25 29 content: { 30 + outline: "none", 26 31 position: "relative", 27 32 paddingBottom: spacing["2"], 28 33 paddingLeft: spacing["2"], ··· 41 46 hideDelay?: number; 42 47 } 43 48 49 + /** Ignore leave events for this long after opening to avoid spurious pointerleave from layout shifts when popover mounts */ 50 + const IGNORE_LEAVE_AFTER_OPEN_MS = 150; 51 + 44 52 function HoverCardInner({ 45 53 trigger, 46 54 triggerName, ··· 62 70 const popoverStyles = usePopoverStyles(); 63 71 const showTimeoutRef = useRef<NodeJS.Timeout | null>(null); 64 72 const hideTimeoutRef = useRef<NodeJS.Timeout | null>(null); 73 + const openedAtRef = useRef<number | null>(null); 65 74 const { hoverProps } = useHover({ 66 75 onHoverStart: () => { 67 76 if (showTimeoutRef.current) return; ··· 71 80 } 72 81 showTimeoutRef.current = setTimeout(() => { 73 82 overlayTriggerState?.open(); 83 + openedAtRef.current = Date.now(); 74 84 showTimeoutRef.current = null; 75 85 }, showDelay); 76 86 }, 77 87 onHoverEnd: () => { 88 + // Ignore leave shortly after opening - popover mount can cause spurious pointerleave 89 + // (e.g. from scroll lock shifting layout or DOM updates when overlay appears) 90 + if ( 91 + openedAtRef.current && 92 + Date.now() - openedAtRef.current < IGNORE_LEAVE_AFTER_OPEN_MS 93 + ) { 94 + return; 95 + } 96 + openedAtRef.current = null; 78 97 if (showTimeoutRef.current) { 79 98 clearTimeout(showTimeoutRef.current); 80 99 showTimeoutRef.current = null;
+12 -10
packages/hip-ui/src/components/label/index.tsx
··· 1 - import * as stylex from "@stylexjs/stylex"; 2 - import { use } from "react"; 3 - import { 1 + import type { 4 2 LabelProps as AriaLabelProps, 5 - Text, 3 + FieldErrorProps, 6 4 TextProps, 7 - Label as AriaLabel, 8 - FieldError, 9 - FieldErrorProps, 10 5 ValidationResult, 11 6 } from "react-aria-components"; 12 7 8 + import * as stylex from "@stylexjs/stylex"; 9 + import { use } from "react"; 10 + import { Label as AriaLabel, FieldError, Text } from "react-aria-components"; 11 + 12 + import type { Size, StyleXComponentProps } from "../theme/types"; 13 + 13 14 import { SizeContext } from "../context"; 15 + import { uiColor } from "../theme/color.stylex"; 14 16 import { critical, ui } from "../theme/semantic-color.stylex"; 15 - import { Size, StyleXComponentProps } from "../theme/types"; 16 17 import { fontSize, fontWeight, lineHeight } from "../theme/typography.stylex"; 17 18 18 19 const styles = stylex.create({ 19 20 label: { 21 + color: uiColor.text1, 20 22 fontWeight: fontWeight["semibold"], 21 23 22 24 fontSize: { 23 - ":is([data-size=lg])": fontSize["base"], 24 - ":is([data-size=md])": fontSize["sm"], 25 + ":is([data-size=lg])": fontSize["sm"], 26 + ":is([data-size=md])": fontSize["xs"], 25 27 ":is([data-size=sm])": fontSize["xs"], 26 28 }, 27 29 lineHeight: {
+10 -2
packages/hip-ui/src/components/navbar/Navbar.tsx
··· 94 94 ":is([data-navbar-open])": `min-content min-content min-content`, 95 95 ":is([data-navbar-open]):has([data-navbar-action])": `min-content min-content min-content min-content`, 96 96 }, 97 - rowGap: spacing["8"], 97 + rowGap: { 98 + default: spacing["4"], 99 + [containerBreakpoints.sm]: spacing["8"], 100 + }, 98 101 marginLeft: "auto", 99 102 marginRight: "auto", 100 103 maxWidth: "var(--page-content-max-width)", ··· 422 425 props.onClick?.(e); 423 426 }} 424 427 > 425 - <span {...stylex.props(styles.linkContent)}>{props.children}</span> 428 + <span {...stylex.props(styles.linkContent)}> 429 + {typeof props.children === "function" 430 + ? props.children({} as any) 431 + : props.children} 432 + </span> 426 433 </Link> 427 434 ); 428 435 } ··· 496 503 style={styles.separator as unknown as stylex.StyleXStyles} 497 504 /> 498 505 <IconButton 506 + size="lg" 499 507 aria-label="Open menu" 500 508 variant="tertiary" 501 509 style={styles.hamburgerButton}
+44 -19
packages/hip-ui/src/components/number-field/index.tsx
··· 88 88 input: { 89 89 cursor: "text", 90 90 }, 91 - buttons: { 92 - display: "flex", 93 - }, 94 91 button: { 95 92 padding: 0, 96 - borderWidth: 0, 97 93 alignItems: "center", 98 94 aspectRatio: 1, 99 95 display: "flex", 100 96 flexGrow: 1, 97 + flexShrink: 0, 101 98 justifyContent: "center", 102 99 borderBottomWidth: 0, 103 100 borderLeftStyle: "solid", 104 - borderLeftWidth: 1, 101 + borderLeftWidth: 0, 105 102 borderRightWidth: 0, 106 103 borderTopWidth: 0, 107 104 minHeight: 0, ··· 119 116 ":disabled": uiColor.text1, 120 117 }, 121 118 }, 119 + buttonLeft: { 120 + borderRightStyle: "solid", 121 + borderRightWidth: 1, 122 + }, 123 + buttonRight: { 124 + borderLeftStyle: "solid", 125 + borderLeftWidth: 1, 126 + }, 127 + inputWithStepper: { 128 + textAlign: "center", 129 + paddingLeft: spacing["2"], 130 + paddingRight: spacing["2"], 131 + }, 122 132 }); 123 133 124 134 interface NumberFieldContentProps { ··· 154 164 variant, 155 165 validationState: isInvalid ? "invalid" : validationState, 156 166 }); 157 - const buttonStyles = stylex.props( 158 - styles.button, 159 - ui.borderInteractive, 160 - ui.bgAction, 161 - ); 162 167 163 168 return ( 164 169 <> ··· 171 176 style={inputStyles.wrapper} 172 177 onClick={() => inputRef.current?.focus()} 173 178 > 179 + {!hideStepper && ( 180 + <Button 181 + slot="decrement" 182 + {...stylex.props( 183 + styles.button, 184 + styles.buttonLeft, 185 + ui.borderInteractive, 186 + ui.bgAction, 187 + )} 188 + > 189 + <Minus /> 190 + </Button> 191 + )} 174 192 {prefix != null && ( 175 193 <div {...stylex.props(inputStyles.addon)}>{prefix}</div> 176 194 )} 177 195 <Input 178 196 placeholder={placeholder} 179 197 ref={inputRef} 180 - {...stylex.props(styles.input, inputStyles.input)} 198 + {...stylex.props( 199 + styles.input, 200 + inputStyles.input, 201 + !hideStepper && styles.inputWithStepper, 202 + )} 181 203 /> 182 204 <SuffixIcon 183 205 suffix={suffix} ··· 186 208 validationState={validationState} 187 209 /> 188 210 {!hideStepper && ( 189 - <Group {...stylex.props(styles.buttons)}> 190 - <Button slot="decrement" {...buttonStyles}> 191 - <Minus /> 192 - </Button> 193 - <Button slot="increment" {...buttonStyles}> 194 - <Plus /> 195 - </Button> 196 - </Group> 211 + <Button 212 + slot="increment" 213 + {...stylex.props( 214 + styles.button, 215 + styles.buttonRight, 216 + ui.borderInteractive, 217 + ui.bgAction, 218 + )} 219 + > 220 + <Plus /> 221 + </Button> 197 222 )} 198 223 </NumberInputWrapper> 199 224 <Description>{description}</Description>
+6 -6
packages/hip-ui/src/components/separator/index.tsx
··· 1 + import type { SeparatorProps as AriaSeparatorProps } from "react-aria-components"; 2 + 1 3 import * as stylex from "@stylexjs/stylex"; 2 - import { 3 - SeparatorProps as AriaSeparatorProps, 4 - Separator as AriaSeparator, 5 - } from "react-aria-components"; 4 + import { Separator as AriaSeparator } from "react-aria-components"; 5 + 6 + import type { StyleXComponentProps } from "../theme/types"; 6 7 7 8 import { uiColor } from "../theme/color.stylex"; 8 - import { StyleXComponentProps } from "../theme/types"; 9 9 10 10 const styles = stylex.create({ 11 11 separator: { 12 12 margin: 0, 13 13 borderWidth: 0, 14 - backgroundColor: uiColor.border2, 14 + backgroundColor: uiColor.component3, 15 15 height: { 16 16 default: "1px", 17 17 ":is([aria-orientation=vertical])": "100%",
+7 -6
packages/hip-ui/src/components/switch/index.tsx
··· 1 + import type { SwitchProps as AriaSwitchProps } from "react-aria-components"; 2 + 1 3 import * as stylex from "@stylexjs/stylex"; 2 - import { 3 - SwitchProps as AriaSwitchProps, 4 - Switch as AriaSwitch, 5 - } from "react-aria-components"; 4 + import { Switch as AriaSwitch } from "react-aria-components"; 5 + 6 + import type { StyleXComponentProps } from "../theme/types"; 6 7 7 8 import { animationDuration } from "../theme/animations.stylex"; 8 9 import { primaryColor, uiColor } from "../theme/color.stylex"; ··· 10 11 import { radius } from "../theme/radius.stylex"; 11 12 import { shadow } from "../theme/shadow.stylex"; 12 13 import { spacing } from "../theme/spacing.stylex"; 13 - import { StyleXComponentProps } from "../theme/types"; 14 14 import { typeramp } from "../theme/typography.stylex"; 15 15 16 16 const styles = stylex.create({ 17 17 wrapper: { 18 - gap: spacing["2"], 18 + gap: spacing["3"], 19 19 alignItems: "center", 20 20 display: "flex", 21 21 }, ··· 39 39 transitionTimingFunction: "ease-in-out", 40 40 height: spacing["6"], 41 41 width: spacing["10"], 42 + flexShrink: 0, 42 43 }, 43 44 thumb: { 44 45 borderRadius: radius.full,
+91 -88
packages/hip-ui/src/components/text-area/index.tsx
··· 1 - import * as stylex from "@stylexjs/stylex"; 2 - import { use, useRef } from "react"; 3 - import { 4 - TextArea as AriaTextArea, 1 + import type { 5 2 TextAreaProps as AriaTextAreaProps, 6 3 InputProps, 7 4 TextFieldProps, 8 5 ValidationResult, 6 + } from "react-aria-components"; 7 + 8 + import * as stylex from "@stylexjs/stylex"; 9 + import { use, useLayoutEffect, useRef } from "react"; 10 + import { 11 + TextArea as AriaTextArea, 9 12 TextField as AriaTextField, 10 13 } from "react-aria-components"; 11 14 15 + import type { 16 + InputValidationState, 17 + InputVariant, 18 + Size, 19 + StyleXComponentProps, 20 + } from "../theme/types"; 21 + 12 22 import { SizeContext } from "../context"; 13 23 import { Description, FieldErrorMessage, Label } from "../label"; 14 - import { uiColor } from "../theme/color.stylex"; 15 - import { mediaQueries } from "../theme/media-queries.stylex"; 16 - import { radius } from "../theme/radius.stylex"; 17 - import { ui } from "../theme/semantic-color.stylex"; 18 24 import { spacing } from "../theme/spacing.stylex"; 19 - import { Size, StyleXComponentProps } from "../theme/types"; 20 - import { lineHeight, fontSize, fontFamily } from "../theme/typography.stylex"; 25 + import { fontFamily, lineHeight } from "../theme/typography.stylex"; 26 + import { useInputStyles } from "../theme/useInputStyles"; 21 27 22 28 const styles = stylex.create({ 23 29 wrapper: { 24 - gap: spacing["2"], 25 - display: "flex", 26 - flexDirection: "column", 27 - }, 28 - addon: { 29 - color: ui.textDim, 30 - flexShrink: 0, 31 - height: "100%", 32 - minWidth: spacing["8"], 33 - paddingLeft: { ":first-child": spacing["0.5"] }, 34 - paddingRight: { 35 - ":last-child:has(svg)": spacing["0.5"], 36 - ":last-child": spacing["2"], 37 - }, 38 - 39 - gap: spacing["0.5"], 40 - alignItems: "center", 41 - display: "flex", 42 - justifyContent: "center", 43 - 44 - // eslint-disable-next-line @stylexjs/no-legacy-contextual-styles, @stylexjs/valid-styles 45 - ":is(*) svg": { 46 - flexShrink: 0, 47 - pointerEvents: "none", 48 - height: spacing["4"], 49 - width: spacing["4"], 50 - }, 30 + height: "auto", 51 31 }, 52 - inputWrapper: { 53 - // eslint-disable-next-line @stylexjs/valid-styles 54 - cornerShape: "squircle", 55 - borderRadius: { 56 - default: radius["md"], 57 - [mediaQueries.supportsSquircle]: radius["2xl"], 58 - }, 59 - boxSizing: "border-box", 60 - display: "flex", 61 - flexGrow: 1, 62 - 63 - borderColor: { 64 - default: uiColor.border2, 65 - ":hover": uiColor.border3, 66 - ":focus": uiColor.solid1, 67 - }, 68 - borderStyle: "solid", 69 - borderWidth: 1, 70 - }, 71 - input: { 72 - borderWidth: 0, 73 - outline: "none", 74 - backgroundColor: "transparent", 75 - boxSizing: "border-box", 76 - color: { 77 - "::placeholder": uiColor.text1, 78 - }, 79 - flexGrow: 1, 32 + textarea: { 80 33 fontFamily: fontFamily["sans"], 81 - resize: "none", 82 - 83 - fontSize: { 84 - ":is([data-size=lg])": fontSize["base"], 85 - ":is([data-size=md])": fontSize["sm"], 86 - ":is([data-size=sm])": fontSize["xs"], 87 - }, 88 34 lineHeight: { 89 35 ":is([data-size=lg])": lineHeight["base"], 90 36 ":is([data-size=md])": lineHeight["sm"], 91 37 ":is([data-size=sm])": lineHeight["xs"], 92 38 }, 39 + resize: "none", 93 40 minHeight: { 94 41 ":is([data-size=lg])": spacing["10"], 95 42 ":is([data-size=md])": spacing["8"], 96 43 ":is([data-size=sm])": spacing["6"], 97 44 }, 45 + minWidth: 0, 98 46 paddingBottom: { 99 - ":is([data-size=lg])": spacing["3"], 100 - ":is([data-size=md])": spacing["2"], 101 - ":is([data-size=sm])": spacing["1"], 102 - }, 103 - paddingLeft: { 104 - ":is([data-size=lg])": spacing["3"], 105 - ":is([data-size=md])": spacing["2"], 106 - ":is([data-size=sm])": spacing["1"], 107 - }, 108 - paddingRight: { 109 47 ":is([data-size=lg])": spacing["3"], 110 48 ":is([data-size=md])": spacing["2"], 111 49 ":is([data-size=sm])": spacing["1"], ··· 115 53 ":is([data-size=md])": spacing["2"], 116 54 ":is([data-size=sm])": spacing["1"], 117 55 }, 56 + width: "100%", 118 57 }, 119 58 resizable: { 120 59 resize: "both", 60 + }, 61 + autosize: { 62 + overflow: "hidden", 63 + resize: "none", 121 64 }, 122 65 }); 123 66 ··· 133 76 prefix?: React.ReactNode; 134 77 suffix?: React.ReactNode; 135 78 isResizable?: boolean; 79 + autosize?: boolean; 80 + variant?: InputVariant; 81 + validationState?: InputValidationState; 136 82 } 137 83 138 84 export function TextArea({ ··· 146 92 placeholder, 147 93 rows, 148 94 isResizable = true, 95 + autosize = true, 96 + variant, 97 + validationState, 149 98 ...props 150 99 }: TextAreaProps) { 151 100 const textAreaRef = useRef<HTMLTextAreaElement>(null); 152 101 const size = sizeProp || use(SizeContext); 102 + const inputStyles = useInputStyles({ size, variant, validationState }); 103 + 104 + useLayoutEffect(() => { 105 + const textarea = textAreaRef.current; 106 + if (!textarea || !autosize) return; 107 + 108 + const adjustHeight = () => { 109 + // Reset height to auto to get accurate scrollHeight 110 + textarea.style.height = "auto"; 111 + // Set height to scrollHeight, which will respect minHeight from styles 112 + const newHeight = textarea.scrollHeight; 113 + textarea.style.height = `${String(newHeight)}px`; 114 + }; 115 + 116 + // Adjust height immediately 117 + adjustHeight(); 118 + 119 + // Listen for input events to adjust height dynamically 120 + textarea.addEventListener("input", adjustHeight); 121 + 122 + // Also adjust on resize in case container size changes 123 + const resizeObserver = new ResizeObserver(adjustHeight); 124 + resizeObserver.observe(textarea); 125 + 126 + return () => { 127 + textarea.removeEventListener("input", adjustHeight); 128 + resizeObserver.disconnect(); 129 + }; 130 + }, [autosize, props.value, props.defaultValue]); 131 + 132 + // Handle onChange to trigger resize when value changes programmatically 133 + const handleChange = (value: string) => { 134 + props.onChange?.(value); 135 + // Trigger resize after value update 136 + if (autosize && textAreaRef.current) { 137 + requestAnimationFrame(() => { 138 + const textarea = textAreaRef.current; 139 + if (textarea) { 140 + textarea.style.height = "auto"; 141 + textarea.style.height = `${String(textarea.scrollHeight)}px`; 142 + } 143 + }); 144 + } 145 + }; 153 146 154 147 return ( 155 148 <SizeContext value={size}> 156 - <AriaTextField {...props} {...stylex.props(styles.wrapper, style)}> 149 + <AriaTextField 150 + {...props} 151 + onChange={props.onChange ? handleChange : undefined} 152 + isInvalid={validationState ? validationState === "invalid" : undefined} 153 + {...stylex.props(inputStyles.field, style)} 154 + > 157 155 <Label>{label}</Label> 158 156 {/* 159 157 This onClick is specifically for mouse users not clicking directly on the input. ··· 161 159 */} 162 160 {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */} 163 161 <div 164 - {...stylex.props(styles.inputWrapper, ui.bgUi, ui.text)} 162 + {...stylex.props(inputStyles.wrapper, styles.wrapper)} 165 163 onClick={() => textAreaRef.current?.focus()} 166 164 > 167 165 {prefix != null && ( 168 - <div {...stylex.props(styles.addon)}>{prefix}</div> 166 + <div {...stylex.props(inputStyles.addon)}>{prefix}</div> 169 167 )} 170 168 <AriaTextArea 171 169 data-size={size} 172 - {...stylex.props(styles.input, isResizable && styles.resizable)} 170 + {...stylex.props( 171 + inputStyles.input, 172 + styles.textarea, 173 + isResizable && !autosize && styles.resizable, 174 + autosize && styles.autosize, 175 + )} 173 176 ref={textAreaRef} 174 177 placeholder={placeholder} 175 - rows={rows} 178 + rows={autosize ? 1 : rows} 176 179 /> 177 180 {suffix != null && ( 178 - <div {...stylex.props(styles.addon)}>{suffix}</div> 181 + <div {...stylex.props(inputStyles.addon)}>{suffix}</div> 179 182 )} 180 183 </div> 181 184 <Description>{description}</Description>
+16 -12
packages/hip-ui/src/components/text-field/index.tsx
··· 1 - import * as stylex from "@stylexjs/stylex"; 2 - import { Eye, EyeOff } from "lucide-react"; 3 - import { useRef, useState, use } from "react"; 4 - import { 1 + import type { 5 2 TextFieldProps as AriaTextFieldProps, 6 - InputContext, 7 3 InputProps, 8 4 ValidationResult, 9 - Input, 10 - TextField as AriaTextField, 11 5 } from "react-aria-components"; 12 6 13 - import { SizeContext } from "../context"; 14 - import { IconButton } from "../icon-button"; 15 - import { Description, FieldErrorMessage, Label } from "../label"; 16 - import { SuffixIcon } from "../suffix-icon"; 7 + import * as stylex from "@stylexjs/stylex"; 8 + import { Eye, EyeOff } from "lucide-react"; 9 + import { use, useRef, useState } from "react"; 17 10 import { 11 + TextField as AriaTextField, 12 + Input, 13 + InputContext, 14 + } from "react-aria-components"; 15 + 16 + import type { 18 17 InputValidationState, 19 18 InputVariant, 20 19 Size, 21 20 StyleXComponentProps, 22 21 } from "../theme/types"; 22 + 23 + import { SizeContext } from "../context"; 24 + import { IconButton } from "../icon-button"; 25 + import { Description, FieldErrorMessage, Label } from "../label"; 26 + import { SuffixIcon } from "../suffix-icon"; 23 27 import { useInputStyles } from "../theme/useInputStyles"; 24 28 25 29 function PasswordToggle({ ··· 123 127 /> 124 128 </div> 125 129 <Description>{description}</Description> 126 - <FieldErrorMessage>{errorMessage}</FieldErrorMessage> 130 + {errorMessage && <FieldErrorMessage>{errorMessage}</FieldErrorMessage>} 127 131 </> 128 132 ); 129 133 }
+18 -14
packages/hip-ui/src/components/theme/semantic-color.stylex.tsx
··· 8 8 uiColor, 9 9 warningColor, 10 10 } from "./color.stylex"; 11 - import { green } from "./colors/green.stylex"; 12 11 import { red } from "./colors/red.stylex"; 13 12 import { yellow } from "./colors/yellow.stylex"; 14 13 import { fontFamily } from "./typography.stylex"; ··· 26 25 borderWidth: 1, 27 26 }, 28 27 border: { 29 - borderColor: uiColor.border2, 28 + borderColor: uiColor.border1, 30 29 borderStyle: "solid", 31 30 borderWidth: 1, 32 31 }, 33 32 borderInteractive: { 34 33 borderColor: { 35 - default: uiColor.border2, 36 - ":hover": uiColor.border3, 34 + default: uiColor.border1, 35 + ":hover": uiColor.border2, 37 36 }, 38 37 borderStyle: "solid", 39 38 borderWidth: 1, ··· 106 105 borderWidth: 1, 107 106 }, 108 107 border: { 109 - borderColor: primaryColor.border2, 108 + borderColor: primaryColor.border1, 110 109 borderStyle: "solid", 111 110 borderWidth: 1, 112 111 }, ··· 191 190 borderWidth: 1, 192 191 }, 193 192 border: { 194 - borderColor: criticalColor.border2, 193 + borderColor: criticalColor.border1, 195 194 borderStyle: "solid", 196 195 borderWidth: 1, 197 196 }, 198 197 borderInteractive: { 199 198 borderColor: { 200 - default: criticalColor.border2, 201 - ":hover": red.border3, 199 + default: criticalColor.border1, 200 + ":hover": red.border1, 202 201 }, 203 202 borderStyle: "solid", 204 203 borderWidth: 1, ··· 276 275 borderWidth: 1, 277 276 }, 278 277 border: { 279 - borderColor: warningColor.border2, 278 + borderColor: warningColor.border1, 280 279 borderStyle: "solid", 281 280 borderWidth: 1, 282 281 }, 283 282 borderInteractive: { 284 283 borderColor: { 285 - default: warningColor.border2, 286 - ":hover": warningColor.border3, 284 + default: warningColor.border1, 285 + ":hover": warningColor.border2, 287 286 }, 287 + borderStyle: "solid", 288 + borderWidth: 1, 289 + transitionDuration: animationDuration.fast, 290 + transitionProperty: "background-color", 291 + transitionTimingFunction: "ease-in-out", 288 292 }, 289 293 bgSolid: { backgroundColor: warningColor.solid1 }, 290 294 bgSolidDark: { backgroundColor: warningColor.solid2 }, ··· 355 359 borderWidth: 1, 356 360 }, 357 361 border: { 358 - borderColor: successColor.border2, 362 + borderColor: successColor.border1, 359 363 borderStyle: "solid", 360 364 borderWidth: 1, 361 365 }, 362 366 borderInteractive: { 363 367 borderColor: { 364 - default: successColor.border2, 365 - ":hover": green.border3, 368 + default: successColor.border1, 369 + ":hover": successColor.border2, 366 370 }, 367 371 borderStyle: "solid", 368 372 borderWidth: 1,
+21 -15
packages/hip-ui/src/components/theme/useButtonStyles.ts
··· 3 3 import * as stylex from "@stylexjs/stylex"; 4 4 import { use } from "react"; 5 5 6 + import type { ButtonVariant, Size } from "../theme/types"; 7 + 6 8 import { ButtonGroupContext } from "../button/context"; 7 9 import { SizeContext } from "../context"; 8 - import { Size, ButtonVariant } from "../theme/types"; 9 10 import { animationDuration } from "./animations.stylex"; 10 11 import { uiColor } from "./color.stylex"; 11 12 import { mediaQueries } from "./media-queries.stylex"; 12 13 import { radius } from "./radius.stylex"; 13 - import { critical, ui, primary } from "./semantic-color.stylex"; 14 + import { critical, primary, ui } from "./semantic-color.stylex"; 14 15 import { shadow } from "./shadow.stylex"; 15 16 import { spacing } from "./spacing.stylex"; 16 - import { 17 - fontFamily, 18 - fontSize, 19 - fontWeight, 20 - lineHeight, 21 - } from "./typography.stylex"; 17 + import { fontFamily, fontSize, fontWeight } from "./typography.stylex"; 22 18 23 19 const styles = stylex.create({ 24 20 shadow: { 25 21 boxShadow: shadow["xs"], 26 22 }, 27 23 base: { 28 - // eslint-disable-next-line @stylexjs/valid-styles 29 - cornerShape: "squircle", 30 24 borderRadius: { 31 - default: radius["md"], 25 + default: radius["lg"], 32 26 [mediaQueries.supportsSquircle]: radius["full"], 33 27 }, 34 28 borderStyle: "solid", 35 29 borderWidth: 1, 30 + 31 + cornerShape: "squircle", 36 32 gap: spacing["1"], 37 33 alignItems: "center", 38 34 boxSizing: "border-box", ··· 63 59 }, 64 60 small: { 65 61 fontSize: fontSize["xs"], 66 - lineHeight: lineHeight["xs"], 67 62 height: spacing["7"], 68 63 paddingLeft: { 69 64 default: spacing["2"], ··· 81 76 medium: { 82 77 gap: spacing["1.5"], 83 78 fontSize: fontSize["sm"], 84 - lineHeight: lineHeight["xs"], 85 79 height: spacing["8"], 86 80 paddingLeft: { 87 81 default: spacing["3"], ··· 91 85 }, 92 86 large: { 93 87 gap: spacing["2"], 88 + fontSize: fontSize["sm"], 94 89 height: spacing["10"], 95 90 paddingLeft: { 96 91 default: spacing["4"], ··· 98 93 }, 99 94 paddingRight: spacing["4"], 100 95 }, 96 + xl: { 97 + gap: spacing["2"], 98 + fontSize: fontSize["lg"], 99 + height: spacing["12"], 100 + paddingLeft: { 101 + default: spacing["5"], 102 + ":has(svg+*)": spacing["4"], 103 + }, 104 + paddingRight: spacing["5"], 105 + }, 101 106 secondary: { 102 107 borderColor: { 103 108 default: uiColor.component1, ··· 146 151 size: sizeProp, 147 152 }: { 148 153 variant?: ButtonVariant; 149 - size?: Size; 154 + size?: Size | "xl"; 150 155 }) => { 151 156 const size = sizeProp || use(SizeContext); 152 157 const group = use(ButtonGroupContext); ··· 200 205 ], 201 206 variant === "critical-outline" && [ 202 207 critical.borderInteractive, 203 - critical.bgGhost, 208 + critical.bgUi, 204 209 critical.text, 205 210 styles.shadow, 206 211 ], 207 212 size === "sm" && styles.small, 208 213 size === "md" && styles.medium, 209 214 size === "lg" && styles.large, 215 + size === "xl" && styles.xl, 210 216 group?.variant === "separate" && styles.separate, 211 217 styles.base, 212 218 ];
+14 -14
packages/hip-ui/src/components/theme/useInputStyles.ts
··· 1 1 import * as stylex from "@stylexjs/stylex"; 2 2 import { use } from "react"; 3 3 4 + import type { InputValidationState, InputVariant, Size } from "../theme/types"; 5 + 4 6 import { SizeContext } from "../context"; 5 - import { InputValidationState, InputVariant, Size } from "../theme/types"; 6 7 import { animationDuration } from "./animations.stylex"; 7 8 import { 8 9 criticalColor, ··· 14 15 import { radius } from "./radius.stylex"; 15 16 import { ui } from "./semantic-color.stylex"; 16 17 import { spacing } from "./spacing.stylex"; 17 - import { lineHeight, fontSize } from "./typography.stylex"; 18 + import { fontSize, lineHeight } from "./typography.stylex"; 18 19 19 20 const styles = stylex.create({ 20 21 field: { ··· 77 78 color: warningColor.text1, 78 79 }, 79 80 inputWrapper: { 80 - // eslint-disable-next-line @stylexjs/valid-styles 81 - cornerShape: "squircle", 82 81 padding: 0, 83 82 borderRadius: { 84 83 default: radius["md"], 85 84 [mediaQueries.supportsSquircle]: radius["3xl"], 86 85 }, 87 86 borderWidth: 0, 87 + 88 + cornerShape: "squircle", 88 89 overflow: "hidden", 89 90 boxSizing: "border-box", 90 91 display: "flex", ··· 112 113 }, 113 114 primary: { 114 115 borderColor: { 115 - default: uiColor.border2, 116 - ":has([data-hovered]):not(:has([data-invalid]))": uiColor.border3, 117 - ":focus": uiColor.solid1, 116 + default: uiColor.border1, 117 + ":has([data-hovered]):not(:has([data-invalid]))": uiColor.border2, 118 + ":focus": uiColor.border3, 118 119 }, 119 120 borderStyle: "solid", 120 121 borderWidth: 1, 121 122 backgroundColor: { 122 - default: "transparent", 123 - ":hover:not(:has(* button:hover)):not(:disabled)": uiColor.component2, 124 - ":is(:active,[data-pressed=true]):not(:disabled)": uiColor.component3, 123 + default: uiColor.bg, 124 + ":hover:not(:has(* button:hover)):not(:disabled)": uiColor.component1, 125 + ":is(:active,[data-pressed=true]):not(:disabled)": uiColor.component2, 125 126 ":disabled": "transparent", 126 127 }, 127 128 boxShadow: { ··· 178 179 }, 179 180 secondary: { 180 181 borderColor: { 181 - default: uiColor.component1, 182 - ":hover:not(:has(* button:hover)):not(:disabled)": uiColor.component2, 183 - ":is(:active,[data-pressed=true]):not(:disabled)": uiColor.component3, 184 - ":disabled": uiColor.component1, 182 + default: uiColor.border1, 183 + ":has([data-hovered]):not(:has([data-invalid]))": uiColor.border2, 184 + ":focus": uiColor.border3, 185 185 }, 186 186 borderStyle: "solid", 187 187 borderWidth: 1,
+11 -6
packages/hip-ui/src/components/theme/useListBoxItemStyles.ts
··· 1 1 import * as stylex from "@stylexjs/stylex"; 2 2 import { use } from "react"; 3 3 4 + import type { Size } from "./types"; 5 + 4 6 import { SizeContext } from "../context"; 5 7 import { radius } from "../theme/radius.stylex"; 6 8 import { spacing } from "../theme/spacing.stylex"; ··· 13 15 import { animationDuration } from "./animations.stylex"; 14 16 import { criticalColor, primaryColor, uiColor } from "./color.stylex"; 15 17 import { mediaQueries } from "./media-queries.stylex"; 16 - import { Size } from "./types"; 17 18 18 19 const styles = stylex.create({ 19 20 item: { ··· 39 40 md: { minHeight: spacing["9"] }, 40 41 lg: { minHeight: spacing["12"] }, 41 42 itemInner: { 42 - // eslint-disable-next-line @stylexjs/valid-styles 43 - cornerShape: "squircle", 44 43 borderRadius: { 45 44 default: radius["md"], 46 45 [mediaQueries.supportsSquircle]: radius["3xl"], 47 46 }, 47 + 48 + cornerShape: "squircle", 48 49 gap: spacing["3"], 49 50 alignItems: "center", 50 51 backgroundColor: { 51 52 default: "transparent", 53 + [":is([data-focused]:not([data-disabled]) *)"]: 54 + uiColor.component2, 52 55 [":is([data-react-aria-pressable=true]:hover:not([data-disabled]) *)"]: 53 56 uiColor.component2, 54 57 [":is([data-react-aria-pressable=true]:not([data-disabled]):active *)"]: ··· 69 72 paddingLeft: spacing["3"], 70 73 paddingRight: spacing["3"], 71 74 paddingTop: spacing["2"], 75 + 76 + /* eslint-disable-next-line @stylexjs/no-legacy-contextual-styles, @stylexjs/valid-styles */ 77 + ":is([data-variant=destructive] *) *": { 78 + color: criticalColor.text1, 79 + }, 72 80 }, 73 81 smItemInner: { 74 82 gap: spacing["2"], ··· 101 109 }, 102 110 label: { 103 111 gap: spacing["1.5"], 104 - color: { 105 - [":is([data-variant=destructive] *)"]: criticalColor.text1, 106 - }, 107 112 display: "flex", 108 113 flexDirection: "column", 109 114 flexGrow: 1,
+6 -6
packages/hip-ui/src/components/typography/index.tsx
··· 9 9 useState, 10 10 } from "react"; 11 11 12 + import type { StyleXComponentProps, TextVariant } from "../theme/types"; 13 + 12 14 import { CopyToClipboardButton } from "../copy-to-clipboard-button"; 13 15 import { Flex } from "../flex"; 14 16 import { LinkContext } from "../link/link-context"; ··· 18 20 import { radius } from "../theme/radius.stylex"; 19 21 import { critical, ui } from "../theme/semantic-color.stylex"; 20 22 import { spacing } from "../theme/spacing.stylex"; 21 - import { StyleXComponentProps, TextVariant } from "../theme/types"; 22 23 import { 23 24 fontFamily, 24 25 fontSize, ··· 28 29 29 30 const styles = stylex.create({ 30 31 pre: { 31 - // eslint-disable-next-line @stylexjs/valid-styles 32 - cornerShape: "squircle", 33 32 padding: spacing["4"], 34 33 borderColor: uiColor.border2, 35 34 borderRadius: { ··· 38 37 }, 39 38 borderStyle: "solid", 40 39 borderWidth: 1, 40 + 41 + cornerShape: "squircle", 41 42 position: "relative", 42 43 marginBottom: spacing["8"], 43 44 marginTop: spacing["8"], ··· 82 83 paddingLeft: spacing["1"], 83 84 }, 84 85 inlineCode: { 85 - // eslint-disable-next-line @stylexjs/valid-styles 86 - cornerShape: "squircle", 87 86 borderRadius: { 88 87 default: radius["sm"], 89 88 [mediaQueries.supportsSquircle]: radius["2xl"], 90 89 }, 90 + 91 + cornerShape: "squircle", 91 92 fontSize: "0.95em", 92 93 position: "relative", 93 94 paddingBottom: spacing["1"], ··· 180 181 style: [ 181 182 variant === "secondary" && ui.textDim, 182 183 variant === "critical" && critical.textDim, 183 - styles.underline, 184 184 ], 185 185 }), 186 186 [variant],