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.

Footer

+1510 -123
+64 -117
.cursor/rules/hip-component.mdc
··· 6 6 7 7 ## Component Creation Process 8 8 9 - To create a new hip component follow these steps: 10 - 11 - 1. **Research phase**: First ask if there is documentation for a headless component to look at (e.g., React Aria Components docs) Check https://component.gallery/ for similar implementation 12 - 2. **Component implementation**: Create the component in `packages/hip-ui/src/components/[component-name]/index.tsx` 13 - 3. **Config creation**: Create a config file `[component-name]-config.ts` in the same directory 14 - 4. **Register config**: Import and add the config to `packages/hip-ui/src/cli/install.tsx` in the `COMPONENT_CONFIGS` array 15 - 5. **Build**: Run the build command to compile the component 16 - 6. **Install**: Run `pnpm hip install --all` in the apps/docs directory 17 - 7. **Documentation**: Generate an `.mdx` page with examples in `apps/docs/src/docs/components/[component-name].mdx` 18 - 8. **Examples**: Create example files in `apps/docs/src/examples/[component-name]/` directory 9 + 1. **Component implementation**: Create in `packages/hip-ui/src/components/[component-name]/index.tsx` 10 + 2. **Config creation**: Create `[component-name]-config.ts` with all dependencies 11 + 3. **Register config**: Add to `packages/hip-ui/src/cli/install.tsx` COMPONENT_CONFIGS array 12 + 4. **Documentation**: Create `.mdx` in `apps/docs/src/docs/components/[category]/[component-name].mdx` 13 + 5. **Examples**: Create examples in `apps/docs/src/examples/[component-name]/` 19 14 20 15 ## Component Structure 21 16 ··· 29 24 ### Component File Structure 30 25 31 26 ```typescript 32 - "use client"; // Add this for client components that use React hooks or interactivity 27 + "use client"; // Required for React hooks, interactions, or react-aria-components 33 28 34 29 import * as stylex from "@stylexjs/stylex"; 35 30 import { ComponentFromAria } from "react-aria-components"; 36 - // Import theme tokens and utilities 37 - import { ui, uiColor } from "../theme/semantic-color.stylex"; 31 + import { breakpoints } from "../theme/media-queries.stylex"; // Use breakpoints, not mediaQueries 32 + import { ui } from "../theme/semantic-color.stylex"; 38 33 import { spacing } from "../theme/spacing.stylex"; 39 - import { radius } from "../theme/radius.stylex"; 40 - import { shadow } from "../theme/shadow.stylex"; 41 - import { fontFamily, fontSize, fontWeight } from "../theme/typography.stylex"; 42 - import { animationDuration } from "../theme/animations.stylex"; 43 - import { Size, StyleXComponentProps } from "../theme/types"; 44 - // Import other hip components if needed 45 - import { Flex } from "../flex"; 46 - import { Grid } from "../grid"; 34 + import { StyleXComponentProps } from "../theme/types"; 47 35 48 - // Define styles using stylex.create 49 36 const styles = stylex.create({ 50 37 base: { 51 - // Use theme tokens, not hardcoded values 52 38 padding: spacing["4"], 53 - borderRadius: radius["md"], 54 - backgroundColor: ui.bg, 55 - color: ui.text, 39 + [breakpoints.sm]: { padding: spacing["6"] }, // Responsive styles 56 40 }, 57 41 }); 58 42 59 - // Export interface extending StyleXComponentProps 60 - export interface ComponentProps extends StyleXComponentProps<AriaComponentProps> { 61 - size?: Size; 62 - variant?: ComponentVariant; 63 - } 43 + export interface ComponentProps extends StyleXComponentProps<AriaComponentProps> {} 44 + 45 + export const Component = ({ style, ...props }: ComponentProps) => { 46 + return <AriaComponent {...stylex.props(styles.base, style)} {...props} />; 47 + }; 48 + 49 + // For composite components with subcomponents, export both individual and namespace: 50 + export const ComponentRoot = ({ style, ...props }: ComponentRootProps) => { /* ... */ }; 51 + export const ComponentSection = ({ style, ...props }: ComponentSectionProps) => { /* ... */ }; 64 52 65 - // Export component 66 - export const Component = ({ style, size, variant, ...props }: ComponentProps) => { 67 - return ( 68 - <AriaComponent 69 - {...stylex.props(styles.base, style)} 70 - data-size={size} 71 - {...props} 72 - /> 73 - ); 53 + export const Component = { 54 + Root: ComponentRoot, 55 + Section: ComponentSection, 74 56 }; 75 57 ``` 76 58 ··· 83 65 name: "component-name", 84 66 filepath: "./index.tsx", 85 67 hipDependencies: [ 86 - // List ALL theme files and other hip components used 68 + // List ALL theme files used (only what's imported) 87 69 "../theme/spacing.stylex.tsx", 88 - "../theme/radius.stylex.tsx", 89 70 "../theme/semantic-color.stylex.tsx", 90 - "../theme/typography.stylex.tsx", 91 - "../theme/shadow.stylex.tsx", 92 - "../theme/animations.stylex.tsx", 71 + "../theme/media-queries.stylex.tsx", // If using breakpoints 93 72 "../theme/types.ts", 94 - // Other hip components if imported 95 - "../flex/index.tsx", 96 73 ], 97 74 dependencies: { 98 - // External npm packages required 99 - "react-aria-components": "^1.13.0", 75 + "react-aria-components": "^1.13.0", // Only if used 100 76 }, 101 77 }; 102 78 ``` ··· 105 81 106 82 ### Styling 107 83 108 - - **Always use theme tokens** - Never hardcode colors, spacing, radius, shadows, or typography values 109 - - Use `semantic-color.stylex.tsx` for colors (`ui.*`, `uiColor.*`, `uiInverted.*`) 110 - - Use `spacing.stylex.tsx` for all spacing values 111 - - Use `radius.stylex.tsx` for border radius values 112 - - Use `shadow.stylex.tsx` for box shadows 113 - - Use `typography.stylex.tsx` for font styles 114 - - Use `animations.stylex.tsx` for transitions and animations 115 - - **Layout components**: Prefer `Flex` and `Grid` components over CSS for layout 116 - - **Size handling**: For composite components, use `SizeContext` to propagate size to child components. Only apply `size` prop to the root element 84 + - **Always use theme tokens** - Never hardcode values 85 + - Use `semantic-color.stylex.tsx` for colors (`ui.*`, `uiColor.*`) 86 + - Use `spacing.stylex.tsx`, `radius.stylex.tsx`, `typography.stylex.tsx`, etc. for all values 87 + - **Responsive styles**: Use `breakpoints` from `media-queries.stylex.tsx` (not `mediaQueries`) 88 + - Prefer `Flex` and `Grid` components over CSS for layout 89 + - Use `SizeContext` to propagate size to child components in composite components 117 90 118 91 ### TypeScript 119 92 120 93 - Always extend `StyleXComponentProps<BaseProps>` for component props 121 94 - Use `stylex.StyleXStyles` for style props (never `className` or React `style`) 122 - - Export clear TypeScript interfaces with JSDoc comments for complex props 123 - - Use `data-*` attributes for conditional styling based on props (e.g., `data-size={size}`) 124 - 125 - ### React Aria Components 126 - 127 - - When using React Aria Components, extend the appropriate Aria component props 128 - - Preserve accessibility features from React Aria Components 129 - - Use React Aria's built-in hooks and state management when appropriate 130 - - Follow React Aria Component patterns for composition (e.g., `DialogTrigger`, `Dialog`, `Modal`, `ModalOverlay`) 131 - 132 - ### Icons 133 - 134 - - Use icons from `lucide-react` exclusively 135 - - Import icons as needed: `import { IconName } from "lucide-react";` 136 - - When used in a hip component the size does not need to be set 95 + - Export TypeScript interfaces with JSDoc comments for complex props 96 + - Use `data-*` attributes for conditional styling (e.g., `data-size={size}`) 137 97 138 98 ### Component Composition 139 99 140 - - **Reuse existing components** - Prefer composing with existing hip components rather than redefining functionality 141 - - For composite components with multiple parts (e.g., Card, CardHeader, CardBody), export all related components from the same file 142 - - Use React Context (like `SizeContext`) for sharing props down the component tree when appropriate 100 + - For composite components (e.g., Footer, Card), export both: 101 + 1. Individual components: `export const FooterRoot = ...` 102 + 2. Namespace object: `export const Footer = { Root: FooterRoot, ... }` 103 + - This allows both `Footer.Root` and `FooterRoot` usage patterns 104 + - Export all related subcomponents from the same file 143 105 144 - ### Client Components 106 + ### React Aria & Icons 145 107 146 - - Add `"use client"` directive at the top of files that: 147 - - Use React hooks (`useState`, `useEffect`, etc.) 148 - - Handle user interactions 149 - - Use React Context 150 - - Import from `react-aria-components` (most components need this) 108 + - Extend appropriate Aria component props when using React Aria Components 109 + - Use icons from `lucide-react` exclusively (size handled automatically) 110 + - Add `"use client"` directive for hooks, interactions, or react-aria-components 151 111 152 112 ## Documentation Structure 153 113 154 - ### MDX File Template 114 + ### MDX File 115 + 116 + Place in `apps/docs/src/docs/components/[category]/[component-name].mdx` (e.g., `navigation/footer.mdx`) 155 117 156 118 ```mdx 157 119 --- 158 120 title: ComponentName 159 - description: Brief description of what the component does. 121 + description: Brief description. 160 122 --- 161 123 162 - import { PropDocs } from "../../lib/PropDocs"; 163 - import { Example } from "../../lib/Example"; 164 - import { Basic } from "../../examples/component-name/basic"; 165 - import { FeatureExample } from "../../examples/component-name/feature"; 124 + import { PropDocs } from '../../../lib/PropDocs' 125 + import { Example } from '../../../lib/Example' 126 + import { Basic } from '../../../examples/component-name/basic' 166 127 167 128 <Example src={Basic} /> 168 129 169 130 ## Installation 170 131 171 - Run the following command to add the component to your project. 172 - 173 - (Use triple backticks with bash language tag for the installation command) 132 + ```bash 133 + pnpm hip install component-name 134 + ``` 174 135 175 136 ## Props 176 - 177 - This component is built using [React Aria ComponentName](link-to-docs). 178 137 179 138 <PropDocs components={["ComponentName", "SubComponent"]} /> 180 139 ··· 182 141 183 142 ### Feature Name 184 143 185 - Description of the feature. 186 - 187 144 <Example src={FeatureExample} /> 188 - 189 - ## Related Components 190 - 191 - - [RelatedComponent](/docs/components/related-component) - Description 192 145 ``` 193 146 194 147 ### Example Files 195 148 196 - - Create example files in `apps/docs/src/examples/[component-name]/` 197 - - Start with `basic.tsx` showing the simplest usage 198 - - Create additional examples for each major feature (variants, sizes, states, etc.) 149 + - Create in `apps/docs/src/examples/[component-name]/` 150 + - Start with `basic.tsx` for simplest usage 199 151 - Use imports from `@/components/[component-name]` 200 - - Keep examples focused and minimal 152 + - Use StyleX styles (not inline styles) - import spacing/theme tokens as needed 201 153 202 - ## Testing Checklist 154 + ## Checklist 203 155 204 - Before considering a component complete: 205 - 206 - - [ ] Component follows StyleX patterns with theme tokens 207 - - [ ] TypeScript interfaces are properly defined 208 - - [ ] All dependencies listed in config file 209 - - [ ] Config added to `install.tsx` COMPONENT_CONFIGS array 210 - - [ ] `"use client"` directive added if needed 211 - - [ ] Component works with `SizeContext` if size is supported 212 - - [ ] Basic example created 213 - - [ ] MDX documentation file created with proper structure 214 - - [ ] Related components listed in documentation 215 - - [ ] Component builds successfully 216 - - [ ] Component installs via `pnpm hip install` 156 + - [ ] Component uses theme tokens (no hardcoded values) 157 + - [ ] TypeScript interfaces with JSDoc comments 158 + - [ ] Config file lists all dependencies 159 + - [ ] Registered in `install.tsx` COMPONENT_CONFIGS 160 + - [ ] `"use client"` added if needed 161 + - [ ] Basic example and MDX docs created 162 + - [ ] Uses `breakpoints` (not `mediaQueries`) for responsive styles 163 + - [ ] Composite components export both individual and namespace patterns
+480
apps/docs/src/components/footer/index.tsx
··· 1 + "use client"; 2 + 3 + import * as stylex from "@stylexjs/stylex"; 4 + import { 5 + LinkProps as AriaLinkProps, 6 + Link as AriaLink, 7 + } from "react-aria-components"; 8 + 9 + import { Button } from "../button"; 10 + import { TextField } from "../text-field"; 11 + import { 12 + breakpoints, 13 + containerBreakpoints, 14 + } from "../theme/media-queries.stylex"; 15 + import { ui } from "../theme/semantic-color.stylex"; 16 + import { spacing } from "../theme/spacing.stylex"; 17 + import { StyleXComponentProps } from "../theme/types"; 18 + import { fontFamily, fontSize, fontWeight } from "../theme/typography.stylex"; 19 + import { uiColor } from "../theme/color.stylex"; 20 + 21 + const styles = stylex.create({ 22 + root: { 23 + fontFamily: fontFamily["sans"], 24 + fontSize: fontSize["sm"], 25 + width: "100%", 26 + boxSizing: "border-box", 27 + backgroundColor: uiColor.bgSubtle, 28 + containerType: "inline-size", 29 + }, 30 + footerSection: { 31 + maxWidth: "1280px", 32 + marginLeft: "auto", 33 + marginRight: "auto", 34 + paddingTop: spacing["6"], 35 + paddingBottom: spacing["6"], 36 + paddingLeft: { 37 + default: spacing["6"], 38 + [breakpoints.sm]: spacing["8"], 39 + }, 40 + paddingRight: { 41 + default: spacing["6"], 42 + [breakpoints.sm]: spacing["8"], 43 + }, 44 + display: "flex", 45 + flexDirection: { 46 + default: "column", 47 + [containerBreakpoints.sm]: "row", 48 + ":is([data-footer-centered] *)": "column !important", 49 + }, 50 + justifyContent: "space-between", 51 + gap: { 52 + default: spacing["8"], 53 + [containerBreakpoints.sm]: spacing["4"], 54 + ":is([data-footer-centered] *)": `${spacing["8"]} !important`, 55 + }, 56 + alignItems: { 57 + default: "stretch", 58 + ":is([data-footer-centered] *)": "center", 59 + [containerBreakpoints.sm]: "center", 60 + }, 61 + borderTopWidth: 1, 62 + borderTopStyle: "solid", 63 + borderTopColor: uiColor.border1, 64 + }, 65 + navSection: { 66 + display: "grid", 67 + gridTemplateColumns: { 68 + default: "repeat(2, 1fr)", 69 + [containerBreakpoints.sm]: "repeat(4, 1fr)", 70 + }, 71 + columnGap: spacing["8"], 72 + rowGap: spacing["6"], 73 + }, 74 + section: { 75 + display: "flex", 76 + flexDirection: "column", 77 + gap: spacing["2"], 78 + alignItems: { 79 + ":is([data-footer-centered] *)": "center", 80 + }, 81 + }, 82 + sectionTitle: { 83 + fontSize: fontSize["sm"], 84 + fontWeight: fontWeight["semibold"], 85 + textTransform: "uppercase", 86 + letterSpacing: "0.05em", 87 + margin: 0, 88 + }, 89 + sectionContent: { 90 + display: "flex", 91 + flexDirection: "column", 92 + gap: spacing["1.5"], 93 + alignItems: { 94 + ":is([data-footer-centered] *)": "center", 95 + }, 96 + }, 97 + copyright: { 98 + fontSize: fontSize["xs"], 99 + margin: 0, 100 + }, 101 + socialLinkList: { 102 + display: "flex", 103 + flexDirection: "row", 104 + gap: spacing["4"], 105 + alignItems: "center", 106 + }, 107 + socialLinkItem: { 108 + display: "inline-flex", 109 + alignItems: "center", 110 + justifyContent: "center", 111 + color: { 112 + default: uiColor.text1, 113 + ":hover": uiColor.text2, 114 + }, 115 + transitionProperty: "color", 116 + transitionDuration: "150ms", 117 + transitionTimingFunction: "ease-in-out", 118 + cursor: "pointer", 119 + textDecoration: "none", 120 + // eslint-disable-next-line @stylexjs/no-legacy-contextual-styles, @stylexjs/valid-styles 121 + ":is(*) svg": { 122 + height: spacing["5"], 123 + width: spacing["5"], 124 + }, 125 + }, 126 + logo: { 127 + display: "flex", 128 + alignItems: "center", 129 + justifyContent: "center", 130 + alignSelf: { 131 + default: "start", 132 + ":is([data-footer-centered] *)": "center", 133 + }, 134 + }, 135 + subscribe: { 136 + display: "grid", 137 + gridTemplateAreas: { 138 + default: ` 139 + "title" 140 + "description" 141 + "input" 142 + `, 143 + ":is([data-subscribe-variant=horizontal])": ` 144 + "title input" 145 + "description input" 146 + `, 147 + }, 148 + gridTemplateColumns: { 149 + default: "1fr", 150 + ":is([data-subscribe-variant=horizontal])": "1fr auto", 151 + }, 152 + gap: spacing["2"], 153 + alignItems: { 154 + default: "stretch", 155 + ":is([data-subscribe-variant=horizontal])": "center", 156 + }, 157 + }, 158 + subscribeTitle: { 159 + gridArea: "title", 160 + fontSize: fontSize["sm"], 161 + fontWeight: fontWeight["semibold"], 162 + margin: 0, 163 + }, 164 + subscribeDescription: { 165 + gridArea: "description", 166 + fontSize: fontSize["sm"], 167 + margin: 0, 168 + marginBottom: spacing["2"], 169 + }, 170 + subscribeInput: { 171 + gridArea: "input", 172 + display: "flex", 173 + flexDirection: "row", 174 + gap: spacing["2"], 175 + alignItems: "flex-start", 176 + }, 177 + subscribeInputField: { 178 + flex: "1", 179 + }, 180 + }); 181 + 182 + /** 183 + * Footer root component. Main container for footer content. 184 + */ 185 + export interface FooterRootProps 186 + extends StyleXComponentProps<React.ComponentProps<"footer">> { 187 + /** 188 + * Centers all footer content. 189 + */ 190 + isCentered?: boolean; 191 + } 192 + 193 + export const FooterRoot = ({ 194 + style, 195 + isCentered, 196 + ...props 197 + }: FooterRootProps) => { 198 + return ( 199 + <footer 200 + {...props} 201 + data-footer-centered={isCentered || undefined} 202 + {...stylex.props(styles.root, ui.bgSubtle, ui.text, style)} 203 + /> 204 + ); 205 + }; 206 + 207 + /** 208 + * Footer logo component. Displays a logo in the footer. 209 + */ 210 + export interface FooterLogoProps 211 + extends StyleXComponentProps<React.ComponentProps<"div">> {} 212 + 213 + export const FooterLogo = ({ style, children, ...props }: FooterLogoProps) => { 214 + return ( 215 + <div {...props} {...stylex.props(styles.logo, style)}> 216 + {children} 217 + </div> 218 + ); 219 + }; 220 + 221 + /** 222 + * Footer section component. Generic container for footer content with max-width, margin, and padding. 223 + * Automatically adds a dim bottom border if it's not the last section. 224 + */ 225 + export interface FooterSectionProps 226 + extends StyleXComponentProps<React.ComponentProps<"div">> {} 227 + 228 + export const FooterSection = ({ style, ...props }: FooterSectionProps) => { 229 + return <div {...props} {...stylex.props(styles.footerSection, style)} />; 230 + }; 231 + 232 + /** 233 + * Footer navigation section component. Responsive grid container that displays 2 columns on smaller screens and 4 columns on larger screens. 234 + */ 235 + export interface FooterNavSectionProps 236 + extends StyleXComponentProps<React.ComponentProps<"div">> {} 237 + 238 + export const FooterNavSection = ({ 239 + style, 240 + ...props 241 + }: FooterNavSectionProps) => { 242 + return <div {...props} {...stylex.props(styles.navSection, style)} />; 243 + }; 244 + 245 + /** 246 + * Footer navigation group component. Container for grouping related footer content. 247 + */ 248 + export interface FooterNavGroupProps 249 + extends StyleXComponentProps<React.ComponentProps<"div">> { 250 + /** 251 + * Optional title for the group. 252 + */ 253 + title?: string; 254 + } 255 + 256 + export const FooterNavGroup = ({ 257 + style, 258 + title, 259 + children, 260 + ...props 261 + }: FooterNavGroupProps) => { 262 + return ( 263 + <div {...props} {...stylex.props(styles.section, style)}> 264 + {title && ( 265 + <h3 {...stylex.props(styles.sectionTitle, ui.text)}>{title}</h3> 266 + )} 267 + <div {...stylex.props(styles.sectionContent)}>{children}</div> 268 + </div> 269 + ); 270 + }; 271 + 272 + /** 273 + * Footer copyright component. Displays copyright information. 274 + */ 275 + export interface FooterCopyrightProps 276 + extends StyleXComponentProps<React.ComponentProps<"p">> {} 277 + 278 + export const FooterCopyright = ({ 279 + style, 280 + children, 281 + ...props 282 + }: FooterCopyrightProps) => { 283 + return ( 284 + <p {...props} {...stylex.props(styles.copyright, ui.textDim, style)}> 285 + {children} 286 + </p> 287 + ); 288 + }; 289 + 290 + /** 291 + * Footer social link list component. Container for social media links. 292 + */ 293 + export interface FooterSocialLinkListProps 294 + extends StyleXComponentProps<React.ComponentProps<"nav">> {} 295 + 296 + export const FooterSocialLinkList = ({ 297 + style, 298 + children, 299 + ...props 300 + }: FooterSocialLinkListProps) => { 301 + return ( 302 + <nav 303 + {...props} 304 + {...stylex.props(styles.socialLinkList, style)} 305 + aria-label="Social links" 306 + > 307 + {children} 308 + </nav> 309 + ); 310 + }; 311 + 312 + /** 313 + * Footer social link item component. Individual social media link. 314 + */ 315 + export interface FooterSocialLinkItemProps 316 + extends StyleXComponentProps<Omit<AriaLinkProps, "children">> { 317 + /** 318 + * Icon to display in the link. Typically from lucide-react. 319 + */ 320 + icon?: React.ReactNode; 321 + /** 322 + * Accessible label for the link. 323 + */ 324 + "aria-label": string; 325 + /** 326 + * Link content (optional, icon can be used alone). 327 + */ 328 + children?: React.ReactNode; 329 + } 330 + 331 + export const FooterSocialLinkItem = ({ 332 + style, 333 + icon, 334 + children, 335 + ...props 336 + }: FooterSocialLinkItemProps) => { 337 + return ( 338 + <AriaLink 339 + {...props} 340 + {...stylex.props(styles.socialLinkItem, style)} 341 + target="_blank" 342 + rel="noopener noreferrer" 343 + > 344 + {icon} 345 + {children} 346 + </AriaLink> 347 + ); 348 + }; 349 + 350 + /** 351 + * Footer subscribe component. Container for newsletter subscription section. 352 + */ 353 + export interface FooterSubscribeProps 354 + extends StyleXComponentProps<React.ComponentProps<"div">> { 355 + /** 356 + * Layout variant. "horizontal" displays title/description on left and input on right. "vertical" stacks everything. 357 + */ 358 + variant?: "horizontal" | "vertical"; 359 + } 360 + 361 + export const FooterSubscribe = ({ 362 + style, 363 + variant = "vertical", 364 + children, 365 + ...props 366 + }: FooterSubscribeProps) => { 367 + return ( 368 + <div 369 + {...props} 370 + data-subscribe-variant={variant} 371 + {...stylex.props(styles.subscribe, style)} 372 + > 373 + {children} 374 + </div> 375 + ); 376 + }; 377 + 378 + /** 379 + * Footer subscribe title component. Displays the subscription section title. 380 + */ 381 + export interface FooterSubscribeTitleProps 382 + extends StyleXComponentProps<React.ComponentProps<"h3">> {} 383 + 384 + export const FooterSubscribeTitle = ({ 385 + style, 386 + children, 387 + ...props 388 + }: FooterSubscribeTitleProps) => { 389 + return ( 390 + <h3 {...props} {...stylex.props(styles.subscribeTitle, ui.text, style)}> 391 + {children} 392 + </h3> 393 + ); 394 + }; 395 + 396 + /** 397 + * Footer subscribe description component. Displays the subscription section description. 398 + */ 399 + export interface FooterSubscribeDescriptionProps 400 + extends StyleXComponentProps<React.ComponentProps<"p">> {} 401 + 402 + export const FooterSubscribeDescription = ({ 403 + style, 404 + children, 405 + ...props 406 + }: FooterSubscribeDescriptionProps) => { 407 + return ( 408 + <p 409 + {...props} 410 + {...stylex.props(styles.subscribeDescription, ui.textDim, style)} 411 + > 412 + {children} 413 + </p> 414 + ); 415 + }; 416 + 417 + /** 418 + * Footer subscribe input component. Contains an email input field and subscribe button. 419 + */ 420 + export interface FooterSubscribeInputProps 421 + extends StyleXComponentProps<React.ComponentProps<"form">> { 422 + /** 423 + * Callback function called when the form is submitted. 424 + */ 425 + onSubmit?: (e: React.FormEvent<HTMLFormElement>) => void; 426 + /** 427 + * Placeholder text for the email input. 428 + */ 429 + placeholder?: string; 430 + /** 431 + * Text for the subscribe button. 432 + */ 433 + buttonText?: string; 434 + } 435 + 436 + export const FooterSubscribeInput = ({ 437 + style, 438 + onSubmit, 439 + placeholder = "Enter your email", 440 + buttonText = "Subscribe", 441 + ...props 442 + }: FooterSubscribeInputProps) => { 443 + return ( 444 + <form 445 + {...props} 446 + {...stylex.props(styles.subscribeInput, style)} 447 + onSubmit={(e) => { 448 + e.preventDefault(); 449 + onSubmit?.(e); 450 + }} 451 + > 452 + <TextField 453 + name="email" 454 + type="email" 455 + placeholder={placeholder} 456 + label={null} 457 + style={styles.subscribeInputField} 458 + /> 459 + <Button type="submit">{buttonText}</Button> 460 + </form> 461 + ); 462 + }; 463 + 464 + /** 465 + * Footer component with subcomponents. 466 + */ 467 + export const Footer = { 468 + Root: FooterRoot, 469 + Logo: FooterLogo, 470 + Section: FooterSection, 471 + NavSection: FooterNavSection, 472 + NavGroup: FooterNavGroup, 473 + Copyright: FooterCopyright, 474 + SocialLinkList: FooterSocialLinkList, 475 + SocialLinkItem: FooterSocialLinkItem, 476 + Subscribe: FooterSubscribe, 477 + SubscribeTitle: FooterSubscribeTitle, 478 + SubscribeDescription: FooterSubscribeDescription, 479 + SubscribeInput: FooterSubscribeInput, 480 + };
+75
apps/docs/src/docs/components/navigation/footer.mdx
··· 1 + --- 2 + title: Footer 3 + description: A flexible footer component for displaying site information, navigation links, and social media links. 4 + --- 5 + 6 + import { PropDocs } from '../../../lib/PropDocs' 7 + import { Example } from '../../../lib/Example' 8 + import { Basic } from '../../../examples/footer/basic' 9 + import { WithSocialLinks } from '../../../examples/footer/with-social-links' 10 + import { WithSections } from '../../../examples/footer/with-sections' 11 + import { Centered } from '../../../examples/footer/centered' 12 + import { WithSubscribeVertical } from '../../../examples/footer/with-subscribe-vertical' 13 + import { WithSubscribeHorizontal } from '../../../examples/footer/with-subscribe-horizontal' 14 + import { Comprehensive } from '../../../examples/footer/comprehensive' 15 + 16 + <Example src={Comprehensive} noPadding /> 17 + 18 + ## Installation 19 + 20 + Run the following command to add the footer component to your project. 21 + 22 + ```bash 23 + pnpm hip install footer 24 + ``` 25 + 26 + ## Props 27 + 28 + This is a custom component built for footer layouts. 29 + 30 + <PropDocs components={["FooterRoot", "FooterSection", "FooterNavSection", "FooterNavGroup", "FooterCopyright", "FooterSocialLinkList", "FooterSocialLinkItem", "FooterSubscribe", "FooterSubscribeTitle", "FooterSubscribeDescription", "FooterSubscribeInput"]} /> 31 + 32 + ## Features 33 + 34 + ### Minimal 35 + 36 + A simple footer with just copyright information. 37 + 38 + <Example src={Basic} noPadding /> 39 + 40 + ### With Sections 41 + 42 + Organize footer content into sections with optional titles. 43 + 44 + <Example src={WithSections} noPadding /> 45 + 46 + ### With Social Links 47 + 48 + Add social media links with icons to your footer. 49 + 50 + <Example src={WithSocialLinks} noPadding /> 51 + 52 + ### Centered 53 + 54 + Use the `isCentered` prop to center all footer content. 55 + 56 + <Example src={Centered} noPadding /> 57 + 58 + ### With Subscribe (Vertical) 59 + 60 + Add a newsletter subscription form with vertical layout. 61 + 62 + <Example src={WithSubscribeVertical} noPadding /> 63 + 64 + ### With Subscribe (Horizontal) 65 + 66 + Add a newsletter subscription form with horizontal layout. 67 + 68 + <Example src={WithSubscribeHorizontal} noPadding /> 69 + 70 + ## Related Components 71 + 72 + - [Link](/docs/components/link) - For navigation links in footer sections 73 + - [Navbar](/docs/components/navbar) - For site navigation 74 + - [Grid](/docs/components/grid) - For multi-column footer layouts 75 + - [Flex](/docs/components/flex) - For flexible footer layouts
+21
apps/docs/src/examples/footer/basic.tsx
··· 1 + import { Footer } from "@/components/footer"; 2 + import { spacing } from "../../components/theme/spacing.stylex"; 3 + import * as stylex from "@stylexjs/stylex"; 4 + 5 + const styles = stylex.create({ 6 + footer: { 7 + paddingTop: spacing["8"], 8 + }, 9 + }); 10 + 11 + export function Basic() { 12 + return ( 13 + <Footer.Root style={styles.footer} isCentered> 14 + <Footer.Section> 15 + <Footer.Copyright> 16 + © 2025 Company Name. All rights reserved. 17 + </Footer.Copyright> 18 + </Footer.Section> 19 + </Footer.Root> 20 + ); 21 + }
+67
apps/docs/src/examples/footer/centered.tsx
··· 1 + import { Footer } from "@/components/footer"; 2 + import { spacing } from "../../components/theme/spacing.stylex"; 3 + import * as stylex from "@stylexjs/stylex"; 4 + import { Link } from "../../components/link"; 5 + 6 + const styles = stylex.create({ 7 + footer: { 8 + paddingTop: spacing["8"], 9 + }, 10 + }); 11 + 12 + function Logo() { 13 + return ( 14 + <svg 15 + width="32" 16 + height="32" 17 + viewBox="0 0 120 120" 18 + fill="currentColor" 19 + xmlns="http://www.w3.org/2000/svg" 20 + > 21 + <circle cx="60" cy="60" r="50" stroke="currentColor" strokeWidth="2" /> 22 + </svg> 23 + ); 24 + } 25 + 26 + export function Centered() { 27 + return ( 28 + <Footer.Root style={styles.footer} isCentered> 29 + <Footer.Section> 30 + <Footer.Logo> 31 + <Logo /> 32 + </Footer.Logo> 33 + <Footer.NavSection> 34 + <Footer.NavGroup title="Product"> 35 + <Link href="/features">Features</Link> 36 + <Link href="/pricing">Pricing</Link> 37 + <Link href="/docs">Documentation</Link> 38 + <Link href="/changelog">Changelog</Link> 39 + </Footer.NavGroup> 40 + <Footer.NavGroup title="Company"> 41 + <Link href="/about">About</Link> 42 + <Link href="/blog">Blog</Link> 43 + <Link href="/careers">Careers</Link> 44 + <Link href="/contact">Contact</Link> 45 + </Footer.NavGroup> 46 + <Footer.NavGroup title="Resources"> 47 + <Link href="/support">Support</Link> 48 + <Link href="/community">Community</Link> 49 + <Link href="/tutorials">Tutorials</Link> 50 + <Link href="/guides">Guides</Link> 51 + </Footer.NavGroup> 52 + <Footer.NavGroup title="Legal"> 53 + <Link href="/privacy">Privacy</Link> 54 + <Link href="/terms">Terms</Link> 55 + <Link href="/cookies">Cookies</Link> 56 + <Link href="/licenses">Licenses</Link> 57 + </Footer.NavGroup> 58 + </Footer.NavSection> 59 + </Footer.Section> 60 + <Footer.Section> 61 + <Footer.Copyright> 62 + © 2025 Company Name. All rights reserved. 63 + </Footer.Copyright> 64 + </Footer.Section> 65 + </Footer.Root> 66 + ); 67 + }
+109
apps/docs/src/examples/footer/comprehensive.tsx
··· 1 + import * as stylex from "@stylexjs/stylex"; 2 + import { Facebook, Twitter, Instagram, Github } from "lucide-react"; 3 + 4 + import { Footer } from "@/components/footer"; 5 + import { Link } from "@/components/link"; 6 + import { spacing } from "../../components/theme/spacing.stylex"; 7 + 8 + const styles = stylex.create({ 9 + footer: { 10 + paddingTop: spacing["8"], 11 + }, 12 + }); 13 + 14 + function Logo() { 15 + return ( 16 + <svg 17 + width="32" 18 + height="32" 19 + viewBox="0 0 120 120" 20 + fill="currentColor" 21 + xmlns="http://www.w3.org/2000/svg" 22 + > 23 + <circle cx="60" cy="60" r="50" stroke="currentColor" strokeWidth="2" /> 24 + </svg> 25 + ); 26 + } 27 + 28 + export function Comprehensive() { 29 + return ( 30 + <Footer.Root style={styles.footer}> 31 + <Footer.Section> 32 + <Footer.Logo> 33 + <Logo /> 34 + </Footer.Logo> 35 + <Footer.NavSection> 36 + <Footer.NavGroup title="Product"> 37 + <Link href="/features">Features</Link> 38 + <Link href="/pricing">Pricing</Link> 39 + <Link href="/docs">Documentation</Link> 40 + <Link href="/changelog">Changelog</Link> 41 + </Footer.NavGroup> 42 + <Footer.NavGroup title="Company"> 43 + <Link href="/about">About</Link> 44 + <Link href="/blog">Blog</Link> 45 + <Link href="/careers">Careers</Link> 46 + <Link href="/contact">Contact</Link> 47 + </Footer.NavGroup> 48 + <Footer.NavGroup title="Resources"> 49 + <Link href="/support">Support</Link> 50 + <Link href="/community">Community</Link> 51 + <Link href="/tutorials">Tutorials</Link> 52 + <Link href="/guides">Guides</Link> 53 + </Footer.NavGroup> 54 + <Footer.NavGroup title="Legal"> 55 + <Link href="/privacy">Privacy</Link> 56 + <Link href="/terms">Terms</Link> 57 + <Link href="/cookies">Cookies</Link> 58 + <Link href="/licenses">Licenses</Link> 59 + </Footer.NavGroup> 60 + </Footer.NavSection> 61 + </Footer.Section> 62 + <Footer.Section> 63 + <Footer.Subscribe variant="horizontal"> 64 + <Footer.SubscribeTitle> 65 + Subscribe to our newsletter 66 + </Footer.SubscribeTitle> 67 + <Footer.SubscribeDescription> 68 + The latest news, articles, and resources, sent to your inbox weekly. 69 + </Footer.SubscribeDescription> 70 + <Footer.SubscribeInput 71 + onSubmit={(e) => { 72 + const formData = new FormData(e.currentTarget); 73 + const email = formData.get("email"); 74 + alert(`Subscribed with: ${email}`); 75 + }} 76 + /> 77 + </Footer.Subscribe> 78 + </Footer.Section> 79 + <Footer.Section> 80 + <Footer.Copyright> 81 + © 2025 Company Name. All rights reserved. 82 + </Footer.Copyright> 83 + <Footer.SocialLinkList> 84 + <Footer.SocialLinkItem 85 + href="https://facebook.com" 86 + aria-label="Facebook" 87 + icon={<Facebook />} 88 + /> 89 + <Footer.SocialLinkItem 90 + href="https://twitter.com" 91 + aria-label="Twitter" 92 + icon={<Twitter />} 93 + /> 94 + <Footer.SocialLinkItem 95 + href="https://instagram.com" 96 + aria-label="Instagram" 97 + icon={<Instagram />} 98 + /> 99 + <Footer.SocialLinkItem 100 + href="https://github.com" 101 + aria-label="GitHub" 102 + icon={<Github />} 103 + /> 104 + </Footer.SocialLinkList> 105 + </Footer.Section> 106 + </Footer.Root> 107 + ); 108 + } 109 +
+68
apps/docs/src/examples/footer/with-sections.tsx
··· 1 + import * as stylex from "@stylexjs/stylex"; 2 + 3 + import { Footer } from "@/components/footer"; 4 + import { Link } from "@/components/link"; 5 + import { spacing } from "../../components/theme/spacing.stylex"; 6 + 7 + const styles = stylex.create({ 8 + footer: { 9 + paddingTop: spacing["8"], 10 + }, 11 + }); 12 + 13 + function Logo() { 14 + return ( 15 + <svg 16 + width="32" 17 + height="32" 18 + viewBox="0 0 120 120" 19 + fill="currentColor" 20 + xmlns="http://www.w3.org/2000/svg" 21 + > 22 + <circle cx="60" cy="60" r="50" stroke="currentColor" strokeWidth="2" /> 23 + </svg> 24 + ); 25 + } 26 + 27 + export function WithSections() { 28 + return ( 29 + <Footer.Root style={styles.footer}> 30 + <Footer.Section> 31 + <Footer.Logo> 32 + <Logo /> 33 + </Footer.Logo> 34 + <Footer.NavSection> 35 + <Footer.NavGroup title="Product"> 36 + <Link href="/features">Features</Link> 37 + <Link href="/pricing">Pricing</Link> 38 + <Link href="/docs">Documentation</Link> 39 + <Link href="/changelog">Changelog</Link> 40 + </Footer.NavGroup> 41 + <Footer.NavGroup title="Company"> 42 + <Link href="/about">About</Link> 43 + <Link href="/blog">Blog</Link> 44 + <Link href="/careers">Careers</Link> 45 + <Link href="/contact">Contact</Link> 46 + </Footer.NavGroup> 47 + <Footer.NavGroup title="Resources"> 48 + <Link href="/support">Support</Link> 49 + <Link href="/community">Community</Link> 50 + <Link href="/tutorials">Tutorials</Link> 51 + <Link href="/guides">Guides</Link> 52 + </Footer.NavGroup> 53 + <Footer.NavGroup title="Legal"> 54 + <Link href="/privacy">Privacy</Link> 55 + <Link href="/terms">Terms</Link> 56 + <Link href="/cookies">Cookies</Link> 57 + <Link href="/licenses">Licenses</Link> 58 + </Footer.NavGroup> 59 + </Footer.NavSection> 60 + </Footer.Section> 61 + <Footer.Section> 62 + <Footer.Copyright> 63 + © 2025 Company Name. All rights reserved. 64 + </Footer.Copyright> 65 + </Footer.Section> 66 + </Footer.Root> 67 + ); 68 + }
+45
apps/docs/src/examples/footer/with-social-links.tsx
··· 1 + import * as stylex from "@stylexjs/stylex"; 2 + import { Facebook, Twitter, Instagram, Github } from "lucide-react"; 3 + 4 + import { Footer } from "@/components/footer"; 5 + import { spacing } from "../../components/theme/spacing.stylex"; 6 + 7 + const styles = stylex.create({ 8 + footer: { 9 + paddingTop: spacing["8"], 10 + }, 11 + }); 12 + 13 + export function WithSocialLinks() { 14 + return ( 15 + <Footer.Root style={styles.footer}> 16 + <Footer.Section> 17 + <Footer.Copyright> 18 + © 2025 Company Name. All rights reserved. 19 + </Footer.Copyright> 20 + <Footer.SocialLinkList> 21 + <Footer.SocialLinkItem 22 + href="https://facebook.com" 23 + aria-label="Facebook" 24 + icon={<Facebook />} 25 + /> 26 + <Footer.SocialLinkItem 27 + href="https://twitter.com" 28 + aria-label="Twitter" 29 + icon={<Twitter />} 30 + /> 31 + <Footer.SocialLinkItem 32 + href="https://instagram.com" 33 + aria-label="Instagram" 34 + icon={<Instagram />} 35 + /> 36 + <Footer.SocialLinkItem 37 + href="https://github.com" 38 + aria-label="GitHub" 39 + icon={<Github />} 40 + /> 41 + </Footer.SocialLinkList> 42 + </Footer.Section> 43 + </Footer.Root> 44 + ); 45 + }
+33
apps/docs/src/examples/footer/with-subscribe-horizontal.tsx
··· 1 + import { Footer } from "@/components/footer"; 2 + import { spacing } from "../../components/theme/spacing.stylex"; 3 + import * as stylex from "@stylexjs/stylex"; 4 + 5 + const styles = stylex.create({ 6 + footer: { 7 + paddingTop: spacing["8"], 8 + }, 9 + }); 10 + 11 + export function WithSubscribeHorizontal() { 12 + return ( 13 + <Footer.Root style={styles.footer}> 14 + <Footer.Section> 15 + <Footer.Subscribe variant="horizontal"> 16 + <Footer.SubscribeTitle> 17 + Subscribe to our newsletter 18 + </Footer.SubscribeTitle> 19 + <Footer.SubscribeDescription> 20 + The latest news, articles, and resources, sent to your inbox weekly. 21 + </Footer.SubscribeDescription> 22 + <Footer.SubscribeInput 23 + onSubmit={(e) => { 24 + const formData = new FormData(e.currentTarget); 25 + const email = formData.get("email"); 26 + alert(`Subscribed with: ${email}`); 27 + }} 28 + /> 29 + </Footer.Subscribe> 30 + </Footer.Section> 31 + </Footer.Root> 32 + ); 33 + }
+32
apps/docs/src/examples/footer/with-subscribe-vertical.tsx
··· 1 + import { Footer } from "@/components/footer"; 2 + import { spacing } from "../../components/theme/spacing.stylex"; 3 + import * as stylex from "@stylexjs/stylex"; 4 + 5 + const styles = stylex.create({ 6 + footer: { 7 + paddingTop: spacing["8"], 8 + }, 9 + }); 10 + 11 + export function WithSubscribeVertical() { 12 + return ( 13 + <Footer.Root style={styles.footer}> 14 + <Footer.Section> 15 + <Footer.Subscribe variant="vertical"> 16 + <Footer.SubscribeTitle>Subscribe to our newsletter</Footer.SubscribeTitle> 17 + <Footer.SubscribeDescription> 18 + The latest news, articles, and resources, sent to your inbox weekly. 19 + </Footer.SubscribeDescription> 20 + <Footer.SubscribeInput 21 + onSubmit={(e) => { 22 + const formData = new FormData(e.currentTarget); 23 + const email = formData.get("email"); 24 + alert(`Subscribed with: ${email}`); 25 + }} 26 + /> 27 + </Footer.Subscribe> 28 + </Footer.Section> 29 + </Footer.Root> 30 + ); 31 + } 32 +
+2
packages/hip-ui/src/cli/install.tsx
··· 40 40 import { emptyStateConfig } from "../components/empty-state/empty-state-config.js"; 41 41 import { fileDropZoneConfig } from "../components/file-drop-zone/file-drop-zone-config.js"; 42 42 import { flexConfig } from "../components/flex/flex-config.js"; 43 + import { footerConfig } from "../components/footer/footer-config.js"; 43 44 import { formConfig } from "../components/form/form-config.js"; 44 45 import { gridConfig } from "../components/grid/grid-config.js"; 45 46 import { hoverCardConfig } from "../components/hover-card/hover-card-config.js"; ··· 135 136 aspectRatioConfig, 136 137 colorSwatchConfig, 137 138 fileDropZoneConfig, 139 + footerConfig, 138 140 formConfig, 139 141 tableConfig, 140 142 sliderConfig,
+19
packages/hip-ui/src/components/footer/footer-config.ts
··· 1 + import { ComponentConfig } from "../../types"; 2 + 3 + export const footerConfig: ComponentConfig = { 4 + name: "footer", 5 + filepath: "./index.tsx", 6 + hipDependencies: [ 7 + "../theme/spacing.stylex.tsx", 8 + "../theme/semantic-color.stylex.tsx", 9 + "../theme/typography.stylex.tsx", 10 + "../theme/media-queries.stylex.tsx", 11 + "../theme/types.ts", 12 + "../button/index.tsx", 13 + "../text-field/index.tsx", 14 + ], 15 + dependencies: { 16 + "react-aria-components": "^1.13.0", 17 + }, 18 + }; 19 +
+480
packages/hip-ui/src/components/footer/index.tsx
··· 1 + "use client"; 2 + 3 + import * as stylex from "@stylexjs/stylex"; 4 + import { 5 + LinkProps as AriaLinkProps, 6 + Link as AriaLink, 7 + } from "react-aria-components"; 8 + 9 + import { Button } from "../button"; 10 + import { TextField } from "../text-field"; 11 + import { 12 + breakpoints, 13 + containerBreakpoints, 14 + } from "../theme/media-queries.stylex"; 15 + import { ui } from "../theme/semantic-color.stylex"; 16 + import { spacing } from "../theme/spacing.stylex"; 17 + import { StyleXComponentProps } from "../theme/types"; 18 + import { fontFamily, fontSize, fontWeight } from "../theme/typography.stylex"; 19 + import { uiColor } from "../theme/color.stylex"; 20 + 21 + const styles = stylex.create({ 22 + root: { 23 + fontFamily: fontFamily["sans"], 24 + fontSize: fontSize["sm"], 25 + width: "100%", 26 + boxSizing: "border-box", 27 + backgroundColor: uiColor.bgSubtle, 28 + containerType: "inline-size", 29 + }, 30 + footerSection: { 31 + maxWidth: "1280px", 32 + marginLeft: "auto", 33 + marginRight: "auto", 34 + paddingTop: spacing["6"], 35 + paddingBottom: spacing["6"], 36 + paddingLeft: { 37 + default: spacing["6"], 38 + [breakpoints.sm]: spacing["8"], 39 + }, 40 + paddingRight: { 41 + default: spacing["6"], 42 + [breakpoints.sm]: spacing["8"], 43 + }, 44 + display: "flex", 45 + flexDirection: { 46 + default: "column", 47 + [containerBreakpoints.sm]: "row", 48 + ":is([data-footer-centered] *)": "column !important", 49 + }, 50 + justifyContent: "space-between", 51 + gap: { 52 + default: spacing["8"], 53 + [containerBreakpoints.sm]: spacing["4"], 54 + ":is([data-footer-centered] *)": `${spacing["8"]} !important`, 55 + }, 56 + alignItems: { 57 + default: "stretch", 58 + ":is([data-footer-centered] *)": "center", 59 + [containerBreakpoints.sm]: "center", 60 + }, 61 + borderTopWidth: 1, 62 + borderTopStyle: "solid", 63 + borderTopColor: uiColor.border1, 64 + }, 65 + navSection: { 66 + display: "grid", 67 + gridTemplateColumns: { 68 + default: "repeat(2, 1fr)", 69 + [containerBreakpoints.sm]: "repeat(4, 1fr)", 70 + }, 71 + columnGap: spacing["8"], 72 + rowGap: spacing["6"], 73 + }, 74 + section: { 75 + display: "flex", 76 + flexDirection: "column", 77 + gap: spacing["2"], 78 + alignItems: { 79 + ":is([data-footer-centered] *)": "center", 80 + }, 81 + }, 82 + sectionTitle: { 83 + fontSize: fontSize["sm"], 84 + fontWeight: fontWeight["semibold"], 85 + textTransform: "uppercase", 86 + letterSpacing: "0.05em", 87 + margin: 0, 88 + }, 89 + sectionContent: { 90 + display: "flex", 91 + flexDirection: "column", 92 + gap: spacing["1.5"], 93 + alignItems: { 94 + ":is([data-footer-centered] *)": "center", 95 + }, 96 + }, 97 + copyright: { 98 + fontSize: fontSize["xs"], 99 + margin: 0, 100 + }, 101 + socialLinkList: { 102 + display: "flex", 103 + flexDirection: "row", 104 + gap: spacing["4"], 105 + alignItems: "center", 106 + }, 107 + socialLinkItem: { 108 + display: "inline-flex", 109 + alignItems: "center", 110 + justifyContent: "center", 111 + color: { 112 + default: uiColor.text1, 113 + ":hover": uiColor.text2, 114 + }, 115 + transitionProperty: "color", 116 + transitionDuration: "150ms", 117 + transitionTimingFunction: "ease-in-out", 118 + cursor: "pointer", 119 + textDecoration: "none", 120 + // eslint-disable-next-line @stylexjs/no-legacy-contextual-styles, @stylexjs/valid-styles 121 + ":is(*) svg": { 122 + height: spacing["5"], 123 + width: spacing["5"], 124 + }, 125 + }, 126 + logo: { 127 + display: "flex", 128 + alignItems: "center", 129 + justifyContent: "center", 130 + alignSelf: { 131 + default: "start", 132 + ":is([data-footer-centered] *)": "center", 133 + }, 134 + }, 135 + subscribe: { 136 + display: "grid", 137 + gridTemplateAreas: { 138 + default: ` 139 + "title" 140 + "description" 141 + "input" 142 + `, 143 + ":is([data-subscribe-variant=horizontal])": ` 144 + "title input" 145 + "description input" 146 + `, 147 + }, 148 + gridTemplateColumns: { 149 + default: "1fr", 150 + ":is([data-subscribe-variant=horizontal])": "1fr auto", 151 + }, 152 + gap: spacing["2"], 153 + alignItems: { 154 + default: "stretch", 155 + ":is([data-subscribe-variant=horizontal])": "center", 156 + }, 157 + }, 158 + subscribeTitle: { 159 + gridArea: "title", 160 + fontSize: fontSize["sm"], 161 + fontWeight: fontWeight["semibold"], 162 + margin: 0, 163 + }, 164 + subscribeDescription: { 165 + gridArea: "description", 166 + fontSize: fontSize["sm"], 167 + margin: 0, 168 + marginBottom: spacing["2"], 169 + }, 170 + subscribeInput: { 171 + gridArea: "input", 172 + display: "flex", 173 + flexDirection: "row", 174 + gap: spacing["2"], 175 + alignItems: "flex-start", 176 + }, 177 + subscribeInputField: { 178 + flex: "1", 179 + }, 180 + }); 181 + 182 + /** 183 + * Footer root component. Main container for footer content. 184 + */ 185 + export interface FooterRootProps 186 + extends StyleXComponentProps<React.ComponentProps<"footer">> { 187 + /** 188 + * Centers all footer content. 189 + */ 190 + isCentered?: boolean; 191 + } 192 + 193 + export const FooterRoot = ({ 194 + style, 195 + isCentered, 196 + ...props 197 + }: FooterRootProps) => { 198 + return ( 199 + <footer 200 + {...props} 201 + data-footer-centered={isCentered || undefined} 202 + {...stylex.props(styles.root, ui.bgSubtle, ui.text, style)} 203 + /> 204 + ); 205 + }; 206 + 207 + /** 208 + * Footer logo component. Displays a logo in the footer. 209 + */ 210 + export interface FooterLogoProps 211 + extends StyleXComponentProps<React.ComponentProps<"div">> {} 212 + 213 + export const FooterLogo = ({ style, children, ...props }: FooterLogoProps) => { 214 + return ( 215 + <div {...props} {...stylex.props(styles.logo, style)}> 216 + {children} 217 + </div> 218 + ); 219 + }; 220 + 221 + /** 222 + * Footer section component. Generic container for footer content with max-width, margin, and padding. 223 + * Automatically adds a dim bottom border if it's not the last section. 224 + */ 225 + export interface FooterSectionProps 226 + extends StyleXComponentProps<React.ComponentProps<"div">> {} 227 + 228 + export const FooterSection = ({ style, ...props }: FooterSectionProps) => { 229 + return <div {...props} {...stylex.props(styles.footerSection, style)} />; 230 + }; 231 + 232 + /** 233 + * Footer navigation section component. Responsive grid container that displays 2 columns on smaller screens and 4 columns on larger screens. 234 + */ 235 + export interface FooterNavSectionProps 236 + extends StyleXComponentProps<React.ComponentProps<"div">> {} 237 + 238 + export const FooterNavSection = ({ 239 + style, 240 + ...props 241 + }: FooterNavSectionProps) => { 242 + return <div {...props} {...stylex.props(styles.navSection, style)} />; 243 + }; 244 + 245 + /** 246 + * Footer navigation group component. Container for grouping related footer content. 247 + */ 248 + export interface FooterNavGroupProps 249 + extends StyleXComponentProps<React.ComponentProps<"div">> { 250 + /** 251 + * Optional title for the group. 252 + */ 253 + title?: string; 254 + } 255 + 256 + export const FooterNavGroup = ({ 257 + style, 258 + title, 259 + children, 260 + ...props 261 + }: FooterNavGroupProps) => { 262 + return ( 263 + <div {...props} {...stylex.props(styles.section, style)}> 264 + {title && ( 265 + <h3 {...stylex.props(styles.sectionTitle, ui.text)}>{title}</h3> 266 + )} 267 + <div {...stylex.props(styles.sectionContent)}>{children}</div> 268 + </div> 269 + ); 270 + }; 271 + 272 + /** 273 + * Footer copyright component. Displays copyright information. 274 + */ 275 + export interface FooterCopyrightProps 276 + extends StyleXComponentProps<React.ComponentProps<"p">> {} 277 + 278 + export const FooterCopyright = ({ 279 + style, 280 + children, 281 + ...props 282 + }: FooterCopyrightProps) => { 283 + return ( 284 + <p {...props} {...stylex.props(styles.copyright, ui.textDim, style)}> 285 + {children} 286 + </p> 287 + ); 288 + }; 289 + 290 + /** 291 + * Footer social link list component. Container for social media links. 292 + */ 293 + export interface FooterSocialLinkListProps 294 + extends StyleXComponentProps<React.ComponentProps<"nav">> {} 295 + 296 + export const FooterSocialLinkList = ({ 297 + style, 298 + children, 299 + ...props 300 + }: FooterSocialLinkListProps) => { 301 + return ( 302 + <nav 303 + {...props} 304 + {...stylex.props(styles.socialLinkList, style)} 305 + aria-label="Social links" 306 + > 307 + {children} 308 + </nav> 309 + ); 310 + }; 311 + 312 + /** 313 + * Footer social link item component. Individual social media link. 314 + */ 315 + export interface FooterSocialLinkItemProps 316 + extends StyleXComponentProps<Omit<AriaLinkProps, "children">> { 317 + /** 318 + * Icon to display in the link. Typically from lucide-react. 319 + */ 320 + icon?: React.ReactNode; 321 + /** 322 + * Accessible label for the link. 323 + */ 324 + "aria-label": string; 325 + /** 326 + * Link content (optional, icon can be used alone). 327 + */ 328 + children?: React.ReactNode; 329 + } 330 + 331 + export const FooterSocialLinkItem = ({ 332 + style, 333 + icon, 334 + children, 335 + ...props 336 + }: FooterSocialLinkItemProps) => { 337 + return ( 338 + <AriaLink 339 + {...props} 340 + {...stylex.props(styles.socialLinkItem, style)} 341 + target="_blank" 342 + rel="noopener noreferrer" 343 + > 344 + {icon} 345 + {children} 346 + </AriaLink> 347 + ); 348 + }; 349 + 350 + /** 351 + * Footer subscribe component. Container for newsletter subscription section. 352 + */ 353 + export interface FooterSubscribeProps 354 + extends StyleXComponentProps<React.ComponentProps<"div">> { 355 + /** 356 + * Layout variant. "horizontal" displays title/description on left and input on right. "vertical" stacks everything. 357 + */ 358 + variant?: "horizontal" | "vertical"; 359 + } 360 + 361 + export const FooterSubscribe = ({ 362 + style, 363 + variant = "vertical", 364 + children, 365 + ...props 366 + }: FooterSubscribeProps) => { 367 + return ( 368 + <div 369 + {...props} 370 + data-subscribe-variant={variant} 371 + {...stylex.props(styles.subscribe, style)} 372 + > 373 + {children} 374 + </div> 375 + ); 376 + }; 377 + 378 + /** 379 + * Footer subscribe title component. Displays the subscription section title. 380 + */ 381 + export interface FooterSubscribeTitleProps 382 + extends StyleXComponentProps<React.ComponentProps<"h3">> {} 383 + 384 + export const FooterSubscribeTitle = ({ 385 + style, 386 + children, 387 + ...props 388 + }: FooterSubscribeTitleProps) => { 389 + return ( 390 + <h3 {...props} {...stylex.props(styles.subscribeTitle, ui.text, style)}> 391 + {children} 392 + </h3> 393 + ); 394 + }; 395 + 396 + /** 397 + * Footer subscribe description component. Displays the subscription section description. 398 + */ 399 + export interface FooterSubscribeDescriptionProps 400 + extends StyleXComponentProps<React.ComponentProps<"p">> {} 401 + 402 + export const FooterSubscribeDescription = ({ 403 + style, 404 + children, 405 + ...props 406 + }: FooterSubscribeDescriptionProps) => { 407 + return ( 408 + <p 409 + {...props} 410 + {...stylex.props(styles.subscribeDescription, ui.textDim, style)} 411 + > 412 + {children} 413 + </p> 414 + ); 415 + }; 416 + 417 + /** 418 + * Footer subscribe input component. Contains an email input field and subscribe button. 419 + */ 420 + export interface FooterSubscribeInputProps 421 + extends StyleXComponentProps<React.ComponentProps<"form">> { 422 + /** 423 + * Callback function called when the form is submitted. 424 + */ 425 + onSubmit?: (e: React.FormEvent<HTMLFormElement>) => void; 426 + /** 427 + * Placeholder text for the email input. 428 + */ 429 + placeholder?: string; 430 + /** 431 + * Text for the subscribe button. 432 + */ 433 + buttonText?: string; 434 + } 435 + 436 + export const FooterSubscribeInput = ({ 437 + style, 438 + onSubmit, 439 + placeholder = "Enter your email", 440 + buttonText = "Subscribe", 441 + ...props 442 + }: FooterSubscribeInputProps) => { 443 + return ( 444 + <form 445 + {...props} 446 + {...stylex.props(styles.subscribeInput, style)} 447 + onSubmit={(e) => { 448 + e.preventDefault(); 449 + onSubmit?.(e); 450 + }} 451 + > 452 + <TextField 453 + name="email" 454 + type="email" 455 + placeholder={placeholder} 456 + label={null} 457 + style={styles.subscribeInputField} 458 + /> 459 + <Button type="submit">{buttonText}</Button> 460 + </form> 461 + ); 462 + }; 463 + 464 + /** 465 + * Footer component with subcomponents. 466 + */ 467 + export const Footer = { 468 + Root: FooterRoot, 469 + Logo: FooterLogo, 470 + Section: FooterSection, 471 + NavSection: FooterNavSection, 472 + NavGroup: FooterNavGroup, 473 + Copyright: FooterCopyright, 474 + SocialLinkList: FooterSocialLinkList, 475 + SocialLinkItem: FooterSocialLinkItem, 476 + Subscribe: FooterSubscribe, 477 + SubscribeTitle: FooterSubscribeTitle, 478 + SubscribeDescription: FooterSubscribeDescription, 479 + SubscribeInput: FooterSubscribeInput, 480 + };
+15 -6
pnpm-workspace.yaml
··· 3 3 - packages/* 4 4 5 5 catalog: 6 - "@react-stately/utils": 3.10.8 7 - "@react-types/overlays": 3.9.2 8 - "@stylexjs/stylex": 0.17.0 9 - "@types/node": 24.9.1 10 - "@types/react": 19.2.0 11 - "@types/react-dom": 19.2.0 6 + '@react-stately/utils': 3.10.8 7 + '@react-types/overlays': 3.9.2 8 + '@stylexjs/stylex': 0.17.0 9 + '@types/node': 24.9.1 10 + '@types/react': 19.2.0 11 + '@types/react-dom': 19.2.0 12 12 change-case: 5.4.4 13 13 dedent: 1.7.0 14 14 eslint: 9.38.0 ··· 21 21 react-stately: 3.42.0 22 22 typescript: 5.9.3 23 23 vitest: 3.2.4 24 + 25 + ignoredBuiltDependencies: 26 + - core-js 27 + 28 + onlyBuiltDependencies: 29 + - '@parcel/watcher' 30 + - '@swc/core' 31 + - esbuild 32 + - unrs-resolver