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.

file drop zone

+753 -55
+29 -1
apps/docs/src/components/aspect-ratio/index.tsx
··· 1 1 import * as stylex from "@stylexjs/stylex"; 2 2 3 + import { radius } from "../theme/radius.stylex"; 4 + 3 5 const styles = stylex.create({ 4 - aspectRatio: (aspectRatio: number) => ({ aspectRatio }), 6 + aspectRatio: (aspectRatio: number) => ({ 7 + aspectRatio, 8 + }), 5 9 container: { 6 10 position: "relative", 11 + }, 12 + imageContainer: { 13 + inset: 0, 14 + position: "absolute", 15 + }, 16 + image: { 17 + borderRadius: radius["md"], 18 + height: "100%", 19 + objectFit: "cover", 20 + overflow: "hidden", 7 21 width: "100%", 8 22 }, 9 23 }); ··· 30 44 /> 31 45 ); 32 46 } 47 + 48 + export interface AspectRatioImageProps 49 + extends Omit<React.ComponentProps<"img">, "style" | "className"> { 50 + style?: stylex.StyleXStyles | stylex.StyleXStyles[]; 51 + } 52 + 53 + export function AspectRatioImage({ style, ...props }: AspectRatioImageProps) { 54 + return ( 55 + <div {...stylex.props(styles.imageContainer, style)}> 56 + {/* eslint-disable-next-line jsx-a11y/alt-text */} 57 + <img {...props} {...stylex.props(styles.image, style)} /> 58 + </div> 59 + ); 60 + }
+12 -17
apps/docs/src/components/card/index.tsx
··· 1 1 import * as stylex from "@stylexjs/stylex"; 2 2 import { use } from "react"; 3 3 4 - import { AspectRatio } from "../aspect-ratio"; 4 + import { AspectRatio, AspectRatioImage } from "../aspect-ratio"; 5 5 import { SizeContext } from "../context"; 6 6 import { radius } from "../theme/radius.stylex"; 7 7 import { gray } from "../theme/semantic-color.stylex"; ··· 43 43 alignItems: "center", 44 44 display: "grid", 45 45 gap: "var(--card-gap)", 46 - gridTemplate: ` 47 - 'title action' 48 - 'description action' 49 - `, 46 + gridTemplate: { 47 + default: `'title action'`, 48 + ":has([data-card-header-description])": ` 49 + 'title action' 50 + 'description action' 51 + `, 52 + }, 50 53 }, 51 54 cardHeaderAction: { 52 55 display: "flex", ··· 79 82 gap: spacing["2"], 80 83 justifyContent: "flex-end", 81 84 }, 82 - cardHeaderImageWrapper: {}, 83 - cardHeaderImage: { 84 - borderRadius: radius["md"], 85 - height: "100%", 86 - objectFit: "cover", 87 - overflow: "hidden", 88 - width: "100%", 89 - }, 90 85 }); 91 86 92 87 export interface CardProps ··· 146 141 return ( 147 142 <p 148 143 {...props} 144 + data-card-header-description 149 145 {...stylex.props(styles.cardDescription, gray.textDim, style)} 150 146 /> 151 147 ); ··· 196 192 aspectRatio?: number; 197 193 } 198 194 199 - export const CardImage = ({ style, ...props }: CardImageProps) => { 195 + export const CardImage = ({ style, aspectRatio, ...props }: CardImageProps) => { 200 196 return ( 201 197 <AspectRatio 202 - {...props} 198 + aspectRatio={aspectRatio} 203 199 style={[styles.cardSection as unknown as stylex.StyleXStyles, style]} 204 200 > 205 - {/* eslint-disable-next-line jsx-a11y/alt-text */} 206 - <img {...props} {...stylex.props(styles.cardHeaderImage, style)} /> 201 + <AspectRatioImage {...props} /> 207 202 </AspectRatio> 208 203 ); 209 204 };
+112
apps/docs/src/components/file-drop-zone/index.tsx
··· 1 + "use client"; 2 + 3 + import * as stylex from "@stylexjs/stylex"; 4 + import { 5 + FileTrigger as AriaFileTrigger, 6 + FileTriggerProps as AriaFileTriggerProps, 7 + DropItem, 8 + DropZone, 9 + DropZoneProps, 10 + } from "react-aria-components"; 11 + 12 + import { plum, slate } from "../theme/colors.stylex"; 13 + import { radius } from "../theme/radius.stylex"; 14 + import { spacing } from "../theme/spacing.stylex"; 15 + import { Text } from "../typography/text"; 16 + 17 + async function getFiles(items: DropItem[]): Promise<File[]> { 18 + return Promise.all( 19 + items.filter((item) => item.kind === "file").map((item) => item.getFile()), 20 + ); 21 + } 22 + 23 + const styles = stylex.create({ 24 + dropZone: { 25 + backgroundColor: { 26 + default: slate.bg2, 27 + ":is([data-drop-target])": plum.component1, 28 + }, 29 + borderColor: { 30 + default: slate.border2, 31 + ":is([data-drop-target])": plum.solid1, 32 + }, 33 + borderRadius: radius.md, 34 + borderStyle: { 35 + default: "dashed", 36 + ":is([data-drop-target])": "solid", 37 + }, 38 + borderWidth: 1, 39 + boxSizing: "border-box", 40 + padding: spacing["2"], 41 + 42 + alignItems: "center", 43 + display: "flex", 44 + flexDirection: "column", 45 + justifyContent: "center", 46 + }, 47 + message: { 48 + textAlign: "center", 49 + }, 50 + }); 51 + 52 + interface FileDropZoneProps 53 + extends Omit<AriaFileTriggerProps, "className" | "style">, 54 + Pick<DropZoneProps, "getDropOperation" | "isDisabled"> { 55 + style?: stylex.StyleXStyles | stylex.StyleXStyles[]; 56 + onAddFiles?: (files: File[]) => void; 57 + } 58 + 59 + export const FileDropZone = ({ 60 + children, 61 + style, 62 + onAddFiles, 63 + getDropOperation, 64 + isDisabled, 65 + ...props 66 + }: FileDropZoneProps) => { 67 + return ( 68 + <DropZone 69 + {...stylex.props(styles.dropZone, style)} 70 + getDropOperation={getDropOperation} 71 + isDisabled={isDisabled} 72 + onDrop={(e) => { 73 + void getFiles(e.items).then((files) => { 74 + onAddFiles?.(files); 75 + }); 76 + }} 77 + getDropOperation={(types) => { 78 + console.log(types); 79 + return types.has("image/png") || types.has("image/jpeg") 80 + ? "copy" 81 + : "cancel"; 82 + }} 83 + > 84 + {({ isDropTarget }) => { 85 + if (isDropTarget) { 86 + return ( 87 + <Text 88 + size="xs" 89 + variant="secondary" 90 + weight="medium" 91 + style={styles.message} 92 + > 93 + Drop to upload 94 + </Text> 95 + ); 96 + } 97 + 98 + return ( 99 + <AriaFileTrigger 100 + {...props} 101 + onSelect={(files) => { 102 + // eslint-disable-next-line unicorn/prefer-spread 103 + onAddFiles?.(Array.from(files ?? [])); 104 + }} 105 + > 106 + {children} 107 + </AriaFileTrigger> 108 + ); 109 + }} 110 + </DropZone> 111 + ); 112 + };
+12 -8
apps/docs/src/components/number-field/index.tsx
··· 62 62 variant?: InputVariant; 63 63 prefix?: React.ReactNode; 64 64 suffix?: React.ReactNode; 65 + hideStepper?: boolean; 65 66 } 66 67 67 68 export function NumberField({ ··· 74 75 prefix, 75 76 suffix, 76 77 placeholder, 78 + hideStepper = false, 77 79 ...props 78 80 }: NumberFieldProps) { 79 81 const inputRef = useRef<HTMLInputElement>(null); ··· 107 109 {suffix != null && ( 108 110 <div {...stylex.props(inputStyles.addon)}>{suffix}</div> 109 111 )} 110 - <Group {...stylex.props(styles.buttons)}> 111 - <Button slot="decrement" {...buttonStyles}> 112 - <Minus /> 113 - </Button> 114 - <Button slot="increment" {...buttonStyles}> 115 - <Plus /> 116 - </Button> 117 - </Group> 112 + {!hideStepper && ( 113 + <Group {...stylex.props(styles.buttons)}> 114 + <Button slot="decrement" {...buttonStyles}> 115 + <Minus /> 116 + </Button> 117 + <Button slot="increment" {...buttonStyles}> 118 + <Plus /> 119 + </Button> 120 + </Group> 121 + )} 118 122 </div> 119 123 {description && <Description size={size}>{description}</Description>} 120 124 <FieldError>{errorMessage}</FieldError>
+1
apps/docs/src/components/theme/typography.stylex.tsx
··· 156 156 fontWeight: fontWeight["semibold"], 157 157 letterSpacing: tracking["tight"], 158 158 lineHeight: { default: lineHeight["sm"] }, 159 + margin: 0, 159 160 }, 160 161 sublabel: { 161 162 // eslint-disable-next-line @stylexjs/valid-styles
+1
apps/docs/src/components/theme/useInputStyles.ts
··· 59 59 display: "flex", 60 60 flexGrow: 1, 61 61 lineHeight: lineHeight["none"], 62 + minWidth: 0, 62 63 outline: "none", 63 64 64 65 appearance: {
+410 -3
apps/docs/src/routes/_docs.ecommerce-app.tsx
··· 5 5 Card, 6 6 CardBody, 7 7 CardDescription, 8 + CardFooter, 8 9 CardHeader, 9 10 CardHeaderAction, 10 11 CardImage, ··· 17 18 import { ToggleButtonGroup } from "@/components/toggle-button-group"; 18 19 import { ToggleButton } from "@/components/toggle-button"; 19 20 import { ColorSwatch } from "@/components/color-swatch"; 21 + import { Grid } from "@/components/grid"; 22 + import { Fragment } from "react/jsx-runtime"; 23 + import { Avatar } from "@/components/avatar"; 24 + import { spacing } from "../components/theme/spacing.stylex"; 25 + import { IconButton } from "@/components/icon-button"; 26 + import { Bookmark, Shredder, Upload } from "lucide-react"; 27 + import { Badge } from "@/components/badge"; 28 + import { AspectRatio, AspectRatioImage } from "@/components/aspect-ratio"; 29 + import { LabelText, SmallBody } from "@/components/typography"; 30 + import { Link } from "@/components/link"; 31 + import { TextField } from "@/components/text-field"; 32 + import { NumberField } from "@/components/number-field"; 33 + import { FileDropZone } from "@/components/file-drop-zone"; 34 + import { TextArea } from "@/components/text-area"; 20 35 21 36 const styles = stylex.create({ 37 + heightFull: { 38 + height: "100%", 39 + }, 22 40 grow: { 23 41 flexGrow: 1, 24 42 flexShrink: 0, 25 43 flexBasis: "0%", 26 44 minWidth: 0, 27 45 }, 46 + medium: { 47 + flexGrow: 0.75, 48 + flexShrink: 0, 49 + flexBasis: "0%", 50 + }, 28 51 skinny: { 29 52 flexGrow: 0.5, 30 53 flexShrink: 0, 31 54 flexBasis: "0%", 55 + }, 56 + relative: { 57 + position: "relative", 58 + }, 59 + bottomRight: { 60 + position: "absolute", 61 + bottom: 0, 62 + right: 0, 63 + marginRight: spacing["4"], 64 + marginBottom: spacing["4"], 32 65 }, 33 66 }); 34 67 ··· 54 87 function SmallProductCardWithBuying() { 55 88 return ( 56 89 <Card size="sm"> 57 - <CardImage src="https://images.unsplash.com/photo-1595950653106-6c9ebd614d3a?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=560&h=540&q=80" /> 90 + <div {...stylex.props(styles.relative)}> 91 + <CardImage src="https://images.unsplash.com/photo-1595950653106-6c9ebd614d3a?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=560&h=540&q=80" /> 92 + <div {...stylex.props(styles.bottomRight)}> 93 + <IconButton label="Bookmark" variant="secondary"> 94 + <Bookmark /> 95 + </IconButton> 96 + </div> 97 + </div> 58 98 <CardBody> 59 99 <Flex direction="column" gap="4"> 60 100 <Flex direction="column" gap="2"> ··· 238 278 ); 239 279 } 240 280 281 + function ShoppingCartCard() { 282 + const cart = [ 283 + { 284 + title: "Poncho #4", 285 + image: 286 + "https://images.unsplash.com/photo-1434389677669-e08b4cac3105?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=80&h=80&q=80&crop=entropy", 287 + price: 79, 288 + size: "M", 289 + count: 1, 290 + }, 291 + { 292 + title: "Jeans #8", 293 + image: 294 + "https://images.unsplash.com/photo-1602293589930-45aad59ba3ab?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=80&h=80&q=80&crop=entropy", 295 + price: 59, 296 + size: "30", 297 + count: 2, 298 + }, 299 + { 300 + title: "Sneakers #14", 301 + image: 302 + "https://images.unsplash.com/photo-1549298916-b41d501d3772?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=80&h=80&q=80&crop=center", 303 + price: 116, 304 + size: "8", 305 + count: 1, 306 + }, 307 + ]; 308 + 309 + return ( 310 + <Card size="sm"> 311 + <CardHeader> 312 + <CardTitle>Shopping Cart</CardTitle> 313 + </CardHeader> 314 + <CardBody> 315 + <Flex direction="column" gap="4"> 316 + <Grid columns="auto 1fr auto auto" columnGap="3" alignItems="center"> 317 + {cart.map((item) => ( 318 + <Fragment key={item.title}> 319 + <Avatar 320 + src={item.image} 321 + alt={item.title} 322 + fallback={item.title.charAt(0)} 323 + size="lg" 324 + /> 325 + <Flex direction="column" gap="1"> 326 + <Text weight="medium">{item.title}</Text> 327 + <Text variant="secondary" size="sm"> 328 + Size {item.size} 329 + </Text> 330 + </Flex> 331 + <Select defaultValue={item.count.toString()}> 332 + <SelectItem id="1">1</SelectItem> 333 + <SelectItem id="2">2</SelectItem> 334 + <SelectItem id="3">3</SelectItem> 335 + <SelectItem id="4">4</SelectItem> 336 + <SelectItem id="5">5</SelectItem> 337 + <SelectItem id="6">6</SelectItem> 338 + <SelectItem id="7">7</SelectItem> 339 + <SelectItem id="8">8</SelectItem> 340 + <SelectItem id="9">9</SelectItem> 341 + <SelectItem id="10">10</SelectItem> 342 + </Select> 343 + <Text size="sm" variant="secondary"> 344 + {Intl.NumberFormat("en-US", { 345 + style: "currency", 346 + currency: "USD", 347 + }).format(item.price * item.count)} 348 + </Text> 349 + </Fragment> 350 + ))} 351 + </Grid> 352 + <Separator /> 353 + <Flex gap="2" justify="between"> 354 + <div></div> 355 + <Button>Go to checkout</Button> 356 + </Flex> 357 + </Flex> 358 + </CardBody> 359 + </Card> 360 + ); 361 + } 362 + 363 + function Delivery() { 364 + return ( 365 + <Card size="sm"> 366 + <Flex direction="column" gap="5"> 367 + <CardHeader> 368 + <CardTitle>Delivery</CardTitle> 369 + <CardHeaderAction> 370 + <Badge variant="warning" size="sm"> 371 + Guaranteed 372 + </Badge> 373 + </CardHeaderAction> 374 + </CardHeader> 375 + <CardBody> 376 + <Flex direction="column" gap="1.5"> 377 + <Text weight="semibold">Tomorrow</Text> 378 + <Text variant="secondary" size="sm"> 379 + 12:00 pm - 2:00 pm 380 + </Text> 381 + </Flex> 382 + 383 + <Flex direction="column" gap="1.5"> 384 + <Text weight="semibold">Luna Rodriguez</Text> 385 + <Text variant="secondary" size="sm"> 386 + 9876 Maple Avenue 387 + <br /> 388 + Cityville, WA 54321 389 + </Text> 390 + </Flex> 391 + </CardBody> 392 + <CardImage src="https://workos.imgix.net/images/bc04b345-f225-488d-8a46-1811096d0c3b.png?auto=format&fit=clip&q=90&w=840&h=654" /> 393 + <CardFooter> 394 + <Button variant="secondary">Edit</Button> 395 + <Button>Confirm</Button> 396 + </CardFooter> 397 + </Flex> 398 + </Card> 399 + ); 400 + } 401 + 402 + function Bookmarks() { 403 + const bookmarks = [ 404 + { 405 + title: "Jeans #8", 406 + image: 407 + "https://images.unsplash.com/photo-1602293589930-45aad59ba3ab?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=272&h=272&q=80&crop=entropy", 408 + price: 118, 409 + }, 410 + { 411 + title: "Jacket #3", 412 + image: 413 + "https://images.unsplash.com/photo-1591047139829-d91aecb6caea?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&crop=entropy&w=272&h=272&q=80", 414 + price: 49, 415 + }, 416 + { 417 + title: "Pants #10", 418 + image: 419 + "https://images.unsplash.com/photo-1506629082955-511b1aa562c8?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=272&h=272&q=80", 420 + price: 32, 421 + }, 422 + { 423 + title: "Shirt #11", 424 + image: 425 + "https://images.unsplash.com/photo-1611312449412-6cefac5dc3e4?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=272&h=272&q=80", 426 + price: 39, 427 + }, 428 + ]; 429 + 430 + return ( 431 + <Card size="sm"> 432 + <CardHeader> 433 + <CardTitle>Bookmarks</CardTitle> 434 + <CardHeaderAction> 435 + <Button variant="tertiary">Buy all</Button> 436 + </CardHeaderAction> 437 + </CardHeader> 438 + <CardBody> 439 + <Grid columns="1fr 1fr" columnGap="2"> 440 + {bookmarks.map((bookmark) => ( 441 + <Flex direction="column" gap="2" key={bookmark.title}> 442 + <AspectRatio aspectRatio={1}> 443 + <AspectRatioImage src={bookmark.image} /> 444 + </AspectRatio> 445 + <div> 446 + <Text weight="medium" size="sm"> 447 + {bookmark.title} 448 + </Text> 449 + <Text variant="secondary" size="sm"> 450 + {", "} 451 + {Intl.NumberFormat("en-US", { 452 + style: "currency", 453 + currency: "USD", 454 + }).format(bookmark.price)} 455 + </Text> 456 + </div> 457 + </Flex> 458 + ))} 459 + </Grid> 460 + </CardBody> 461 + </Card> 462 + ); 463 + } 464 + 465 + function DiscardedCard() { 466 + return ( 467 + <Card> 468 + <CardBody> 469 + <Flex direction="column" gap="4" align="center"> 470 + <Shredder size={48} /> 471 + <Text weight="semibold" size="lg"> 472 + Product discarded 473 + </Text> 474 + <SmallBody variant="secondary"> 475 + It's still available in the <Link>archive.</Link> 476 + </SmallBody> 477 + <Flex gap="2"> 478 + <Button variant="secondary">Undo</Button> 479 + <Button>Done</Button> 480 + </Flex> 481 + </Flex> 482 + </CardBody> 483 + </Card> 484 + ); 485 + } 486 + 487 + function EditProductCard() { 488 + return ( 489 + <Card> 490 + <CardHeader> 491 + <CardTitle>Edit product</CardTitle> 492 + </CardHeader> 493 + <CardBody> 494 + <Grid columns="1fr 140px" columnGap="2"> 495 + <TextField label="Title" defaultValue="Sneakers #12" /> 496 + <NumberField 497 + label="Price" 498 + defaultValue={116} 499 + formatOptions={{ 500 + style: "currency", 501 + currency: "USD", 502 + }} 503 + /> 504 + </Grid> 505 + <Flex direction="column" gap="2"> 506 + <LabelText>Media</LabelText> 507 + <Grid columns="1fr 1fr 1fr" columnGap="2"> 508 + <AspectRatio> 509 + <AspectRatioImage src="https://images.unsplash.com/photo-1551163943-3f6a855d1153?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=400&h=400&q=80&crop=bottom" /> 510 + </AspectRatio> 511 + <AspectRatio> 512 + <AspectRatioImage src="https://workos.imgix.net/images/c773ee38-9136-49d1-804c-6d166dad9c65.png?auto=format&fit=clip&q=80w=400&h=400" /> 513 + </AspectRatio> 514 + <AspectRatio> 515 + <FileDropZone style={styles.heightFull}> 516 + <IconButton variant="secondary" label="Upload image"> 517 + <Upload /> 518 + </IconButton> 519 + </FileDropZone> 520 + </AspectRatio> 521 + </Grid> 522 + </Flex> 523 + <TextArea 524 + label="Description" 525 + defaultValue="This is a description of the product." 526 + rows={4} 527 + /> 528 + <Flex direction="column" gap="2"> 529 + <Text weight="semibold">Material</Text> 530 + <ToggleButtonGroup 531 + variant="separate" 532 + itemsPerRow={3} 533 + selectionMode="single" 534 + > 535 + <ToggleButton id="synthetic" variant="secondary"> 536 + Synthetic 537 + </ToggleButton> 538 + <ToggleButton id="wool" variant="secondary"> 539 + Wool 540 + </ToggleButton> 541 + <ToggleButton id="cotton" variant="secondary"> 542 + Cotton 543 + </ToggleButton> 544 + <ToggleButton id="linen" variant="secondary"> 545 + Linen 546 + </ToggleButton> 547 + <ToggleButton id="denim" variant="secondary"> 548 + Denim 549 + </ToggleButton> 550 + <ToggleButton id="leather" variant="secondary"> 551 + Leather 552 + </ToggleButton> 553 + <ToggleButton id="silk" variant="secondary"> 554 + Silk 555 + </ToggleButton> 556 + <ToggleButton id="chiffon" variant="secondary"> 557 + Chiffon 558 + </ToggleButton> 559 + <ToggleButton id="other" variant="secondary"> 560 + Other 561 + </ToggleButton> 562 + </ToggleButtonGroup> 563 + </Flex> 564 + <Flex direction="column" gap="2"> 565 + <Text weight="semibold">Main Color</Text> 566 + <ToggleButtonGroup 567 + variant="separate" 568 + itemsPerRow={3} 569 + selectionMode="single" 570 + > 571 + <ToggleButton id="white" variant="secondary"> 572 + <ColorSwatch color="#fff" size="sm" /> 573 + White 574 + </ToggleButton> 575 + <ToggleButton id="grey" variant="secondary"> 576 + <ColorSwatch color="#808080" size="sm" /> 577 + Grey 578 + </ToggleButton> 579 + <ToggleButton id="black" variant="secondary"> 580 + <ColorSwatch color="#000" size="sm" /> 581 + Black 582 + </ToggleButton> 583 + <ToggleButton id="red" variant="secondary"> 584 + <ColorSwatch color="#f00" size="sm" /> 585 + Red 586 + </ToggleButton> 587 + <ToggleButton id="pink" variant="secondary"> 588 + <ColorSwatch color="#f0f" size="sm" /> 589 + Pink 590 + </ToggleButton> 591 + <ToggleButton id="violet" variant="secondary"> 592 + <ColorSwatch color="#800080" size="sm" /> 593 + Violet 594 + </ToggleButton> 595 + <ToggleButton id="blue" variant="secondary"> 596 + <ColorSwatch color="#00f" size="sm" /> 597 + Blue 598 + </ToggleButton> 599 + <ToggleButton id="green" variant="secondary"> 600 + <ColorSwatch color="#0f0" size="sm" /> 601 + Green 602 + </ToggleButton> 603 + <ToggleButton id="beige" variant="secondary"> 604 + <ColorSwatch color="#f5f5dc" size="sm" /> 605 + Beige 606 + </ToggleButton> 607 + </ToggleButtonGroup> 608 + </Flex> 609 + <Flex direction="column" gap="2"> 610 + <Text weight="semibold">Size</Text> 611 + <ToggleButtonGroup 612 + variant="separate" 613 + itemsPerRow={3} 614 + selectionMode="single" 615 + > 616 + <ToggleButton id="xs" variant="secondary"> 617 + XS 618 + </ToggleButton> 619 + <ToggleButton id="s" variant="secondary"> 620 + S 621 + </ToggleButton> 622 + <ToggleButton id="m" variant="secondary"> 623 + M 624 + </ToggleButton> 625 + <ToggleButton id="l" variant="secondary"> 626 + L 627 + </ToggleButton> 628 + <ToggleButton id="xl" variant="secondary"> 629 + XL 630 + </ToggleButton> 631 + <ToggleButton id="xxl" variant="secondary"> 632 + XXL 633 + </ToggleButton> 634 + </ToggleButtonGroup> 635 + </Flex> 636 + </CardBody> 637 + </Card> 638 + ); 639 + } 640 + 241 641 function RouteComponent() { 242 642 return ( 243 643 <Flex gap="4"> ··· 246 646 <SmallProductCardWithBuying /> 247 647 <ProductOptionsCard /> 248 648 </Flex> 249 - <Flex direction="column" gap="4" style={styles.skinny}></Flex> 250 - <Flex direction="column" gap="4" style={styles.skinny}></Flex> 649 + <Flex direction="column" gap="4" style={styles.skinny}> 650 + <Delivery /> 651 + <Bookmarks /> 652 + <ShoppingCartCard /> 653 + </Flex> 654 + <Flex direction="column" gap="4" style={styles.medium}> 655 + <DiscardedCard /> 656 + <EditProductCard /> 657 + </Flex> 251 658 <Flex direction="column" gap="4" style={styles.grow}></Flex> 252 659 </Flex> 253 660 );
+2
packages/hip-ui/src/cli/install.tsx
··· 23 23 import { contextMenuConfig } from "../components/context-menu/context-menu-config.js"; 24 24 import { dateFieldConfig } from "../components/date-field/date-field-config.js"; 25 25 import { dialogConfig } from "../components/dialog/dialog-config.js"; 26 + import { fileDropZoneConfig } from "../components/file-drop-zone/file-drop-zone-config.js"; 26 27 import { flexConfig } from "../components/flex/flex-config.js"; 27 28 import { gridConfig } from "../components/grid/grid-config.js"; 28 29 import { iconButtonConfig } from "../components/icon-button/icon-button-config.js"; ··· 88 89 switchConfig, 89 90 aspectRatioConfig, 90 91 colorSwatchConfig, 92 + fileDropZoneConfig, 91 93 ]; 92 94 93 95 function StringSetting({
+29 -1
packages/hip-ui/src/components/aspect-ratio/index.tsx
··· 1 1 import * as stylex from "@stylexjs/stylex"; 2 2 3 + import { radius } from "../theme/radius.stylex"; 4 + 3 5 const styles = stylex.create({ 4 - aspectRatio: (aspectRatio: number) => ({ aspectRatio }), 6 + aspectRatio: (aspectRatio: number) => ({ 7 + aspectRatio, 8 + }), 5 9 container: { 6 10 position: "relative", 11 + }, 12 + imageContainer: { 13 + inset: 0, 14 + position: "absolute", 15 + }, 16 + image: { 17 + borderRadius: radius["md"], 18 + height: "100%", 19 + objectFit: "cover", 20 + overflow: "hidden", 7 21 width: "100%", 8 22 }, 9 23 }); ··· 30 44 /> 31 45 ); 32 46 } 47 + 48 + export interface AspectRatioImageProps 49 + extends Omit<React.ComponentProps<"img">, "style" | "className"> { 50 + style?: stylex.StyleXStyles | stylex.StyleXStyles[]; 51 + } 52 + 53 + export function AspectRatioImage({ style, ...props }: AspectRatioImageProps) { 54 + return ( 55 + <div {...stylex.props(styles.imageContainer, style)}> 56 + {/* eslint-disable-next-line jsx-a11y/alt-text */} 57 + <img {...props} {...stylex.props(styles.image, style)} /> 58 + </div> 59 + ); 60 + }
+12 -17
packages/hip-ui/src/components/card/index.tsx
··· 1 1 import * as stylex from "@stylexjs/stylex"; 2 2 import { use } from "react"; 3 3 4 - import { AspectRatio } from "../aspect-ratio"; 4 + import { AspectRatio, AspectRatioImage } from "../aspect-ratio"; 5 5 import { SizeContext } from "../context"; 6 6 import { radius } from "../theme/radius.stylex"; 7 7 import { gray } from "../theme/semantic-color.stylex"; ··· 43 43 alignItems: "center", 44 44 display: "grid", 45 45 gap: "var(--card-gap)", 46 - gridTemplate: ` 47 - 'title action' 48 - 'description action' 49 - `, 46 + gridTemplate: { 47 + default: `'title action'`, 48 + ":has([data-card-header-description])": ` 49 + 'title action' 50 + 'description action' 51 + `, 52 + }, 50 53 }, 51 54 cardHeaderAction: { 52 55 display: "flex", ··· 79 82 gap: spacing["2"], 80 83 justifyContent: "flex-end", 81 84 }, 82 - cardHeaderImageWrapper: {}, 83 - cardHeaderImage: { 84 - borderRadius: radius["md"], 85 - height: "100%", 86 - objectFit: "cover", 87 - overflow: "hidden", 88 - width: "100%", 89 - }, 90 85 }); 91 86 92 87 export interface CardProps ··· 146 141 return ( 147 142 <p 148 143 {...props} 144 + data-card-header-description 149 145 {...stylex.props(styles.cardDescription, gray.textDim, style)} 150 146 /> 151 147 ); ··· 196 192 aspectRatio?: number; 197 193 } 198 194 199 - export const CardImage = ({ style, ...props }: CardImageProps) => { 195 + export const CardImage = ({ style, aspectRatio, ...props }: CardImageProps) => { 200 196 return ( 201 197 <AspectRatio 202 - {...props} 198 + aspectRatio={aspectRatio} 203 199 style={[styles.cardSection as unknown as stylex.StyleXStyles, style]} 204 200 > 205 - {/* eslint-disable-next-line jsx-a11y/alt-text */} 206 - <img {...props} {...stylex.props(styles.cardHeaderImage, style)} /> 201 + <AspectRatioImage {...props} /> 207 202 </AspectRatio> 208 203 ); 209 204 };
+7
packages/hip-ui/src/components/file-drop-zone/file-drop-zone-config.ts
··· 1 + import { ComponentConfig } from "../../types"; 2 + 3 + export const fileDropZoneConfig: ComponentConfig = { 4 + name: "file-drop-zone", 5 + filepath: "./index.tsx", 6 + hipDependencies: [], 7 + };
+112
packages/hip-ui/src/components/file-drop-zone/index.tsx
··· 1 + "use client"; 2 + 3 + import * as stylex from "@stylexjs/stylex"; 4 + import { 5 + FileTrigger as AriaFileTrigger, 6 + FileTriggerProps as AriaFileTriggerProps, 7 + DropItem, 8 + DropZone, 9 + DropZoneProps, 10 + } from "react-aria-components"; 11 + 12 + import { plum, slate } from "../theme/colors.stylex"; 13 + import { radius } from "../theme/radius.stylex"; 14 + import { spacing } from "../theme/spacing.stylex"; 15 + import { Text } from "../typography/text"; 16 + 17 + async function getFiles(items: DropItem[]): Promise<File[]> { 18 + return Promise.all( 19 + items.filter((item) => item.kind === "file").map((item) => item.getFile()), 20 + ); 21 + } 22 + 23 + const styles = stylex.create({ 24 + dropZone: { 25 + backgroundColor: { 26 + default: slate.bg2, 27 + ":is([data-drop-target])": plum.component1, 28 + }, 29 + borderColor: { 30 + default: slate.border2, 31 + ":is([data-drop-target])": plum.solid1, 32 + }, 33 + borderRadius: radius.md, 34 + borderStyle: { 35 + default: "dashed", 36 + ":is([data-drop-target])": "solid", 37 + }, 38 + borderWidth: 1, 39 + boxSizing: "border-box", 40 + padding: spacing["2"], 41 + 42 + alignItems: "center", 43 + display: "flex", 44 + flexDirection: "column", 45 + justifyContent: "center", 46 + }, 47 + message: { 48 + textAlign: "center", 49 + }, 50 + }); 51 + 52 + interface FileDropZoneProps 53 + extends Omit<AriaFileTriggerProps, "className" | "style">, 54 + Pick<DropZoneProps, "isDisabled"> { 55 + style?: stylex.StyleXStyles | stylex.StyleXStyles[]; 56 + onAddFiles?: (files: File[]) => void; 57 + } 58 + 59 + export const FileDropZone = ({ 60 + children, 61 + style, 62 + onAddFiles, 63 + isDisabled, 64 + acceptedFileTypes, 65 + ...props 66 + }: FileDropZoneProps) => { 67 + return ( 68 + <DropZone 69 + {...stylex.props(styles.dropZone, style)} 70 + isDisabled={isDisabled} 71 + onDrop={(e) => { 72 + void getFiles(e.items).then((files) => { 73 + onAddFiles?.(files); 74 + }); 75 + }} 76 + getDropOperation={(types) => { 77 + if (!acceptedFileTypes) return "copy"; 78 + return acceptedFileTypes.some((type) => types.has(type)) 79 + ? "copy" 80 + : "cancel"; 81 + }} 82 + > 83 + {({ isDropTarget }) => { 84 + if (isDropTarget) { 85 + return ( 86 + <Text 87 + size="xs" 88 + variant="secondary" 89 + weight="medium" 90 + style={styles.message} 91 + > 92 + Drop to upload 93 + </Text> 94 + ); 95 + } 96 + 97 + return ( 98 + <AriaFileTrigger 99 + {...props} 100 + acceptedFileTypes={acceptedFileTypes} 101 + onSelect={(files) => { 102 + // eslint-disable-next-line unicorn/prefer-spread 103 + onAddFiles?.(Array.from(files ?? [])); 104 + }} 105 + > 106 + {children} 107 + </AriaFileTrigger> 108 + ); 109 + }} 110 + </DropZone> 111 + ); 112 + };
+12 -8
packages/hip-ui/src/components/number-field/index.tsx
··· 62 62 variant?: InputVariant; 63 63 prefix?: React.ReactNode; 64 64 suffix?: React.ReactNode; 65 + hideStepper?: boolean; 65 66 } 66 67 67 68 export function NumberField({ ··· 74 75 prefix, 75 76 suffix, 76 77 placeholder, 78 + hideStepper = false, 77 79 ...props 78 80 }: NumberFieldProps) { 79 81 const inputRef = useRef<HTMLInputElement>(null); ··· 107 109 {suffix != null && ( 108 110 <div {...stylex.props(inputStyles.addon)}>{suffix}</div> 109 111 )} 110 - <Group {...stylex.props(styles.buttons)}> 111 - <Button slot="decrement" {...buttonStyles}> 112 - <Minus /> 113 - </Button> 114 - <Button slot="increment" {...buttonStyles}> 115 - <Plus /> 116 - </Button> 117 - </Group> 112 + {!hideStepper && ( 113 + <Group {...stylex.props(styles.buttons)}> 114 + <Button slot="decrement" {...buttonStyles}> 115 + <Minus /> 116 + </Button> 117 + <Button slot="increment" {...buttonStyles}> 118 + <Plus /> 119 + </Button> 120 + </Group> 121 + )} 118 122 </div> 119 123 {description && <Description size={size}>{description}</Description>} 120 124 <FieldError>{errorMessage}</FieldError>
+1
packages/hip-ui/src/components/theme/typography.stylex.tsx
··· 156 156 fontWeight: fontWeight["semibold"], 157 157 letterSpacing: tracking["tight"], 158 158 lineHeight: { default: lineHeight["sm"] }, 159 + margin: 0, 159 160 }, 160 161 sublabel: { 161 162 // eslint-disable-next-line @stylexjs/valid-styles
+1
packages/hip-ui/src/components/theme/useInputStyles.ts
··· 59 59 display: "flex", 60 60 flexGrow: 1, 61 61 lineHeight: lineHeight["none"], 62 + minWidth: 0, 62 63 outline: "none", 63 64 64 65 appearance: {