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.

sidebar

+785 -34
+4 -1
apps/docs/src/components/listbox/index.tsx
··· 122 122 <div {...stylex.props(listBoxItemStyles.addon)}> 123 123 <ListBoxCheckbox 124 124 isSelected={isSelected} 125 - id={props.id || (props.value as { id: string })?.id} 125 + id={ 126 + (props.id as string) || 127 + (props.value as { id?: string } | undefined)?.id 128 + } 126 129 onPress={(e) => e.continuePropagation()} 127 130 /> 128 131 </div>
+268
apps/docs/src/components/sidebar/index.tsx
··· 1 + import * as stylex from "@stylexjs/stylex"; 2 + import { ChevronRight } from "lucide-react"; 3 + import { createContext, use, useId, useMemo } from "react"; 4 + import { 5 + Button, 6 + Disclosure, 7 + DisclosurePanel, 8 + Heading, 9 + } from "react-aria-components"; 10 + 11 + import { Flex } from "../flex"; 12 + import { mediaQueries } from "../theme/media-queries.stylex"; 13 + import { radius } from "../theme/radius.stylex"; 14 + import { primaryColor, uiColor } from "../theme/semantic-color.stylex"; 15 + import { spacing } from "../theme/spacing.stylex"; 16 + import { StyleXComponentProps } from "../theme/types"; 17 + import { fontFamily, fontSize, fontWeight } from "../theme/typography.stylex"; 18 + import { Text } from "../typography/text"; 19 + 20 + interface SidebarContextType { 21 + headerId: string; 22 + } 23 + 24 + const SidebarContext = createContext<SidebarContextType>({ 25 + headerId: "", 26 + }); 27 + 28 + const styles = stylex.create({ 29 + sidebar: { 30 + display: "flex", 31 + flexDirection: "column", 32 + gap: spacing["6"], 33 + paddingBottom: spacing["12"], 34 + paddingLeft: spacing["8"], 35 + paddingRight: spacing["8"], 36 + paddingTop: spacing["6"], 37 + width: spacing["64"], 38 + }, 39 + sidebarHeader: { 40 + marginBottom: spacing["4"], 41 + padding: spacing["3"], 42 + }, 43 + sidebarHeaderLink: { 44 + textDecoration: "none", 45 + }, 46 + sidebarSectionTitle: { 47 + height: spacing["6"], 48 + paddingLeft: spacing["3"], 49 + paddingRight: spacing["3"], 50 + }, 51 + sidebarSectionList: { 52 + display: "flex", 53 + flexDirection: "column", 54 + gap: spacing["1"], 55 + margin: 0, 56 + padding: 0, 57 + }, 58 + sidebarItem: { 59 + alignItems: "center", 60 + backgroundColor: { 61 + default: "transparent", 62 + ":hover": uiColor.component2, 63 + ":active": uiColor.component3, 64 + }, 65 + borderRadius: radius["md"], 66 + color: uiColor.text2, 67 + display: "flex", 68 + height: spacing["8"], 69 + listStyle: "none", 70 + paddingLeft: spacing["3"], 71 + paddingRight: spacing["3"], 72 + transitionDuration: "100ms", 73 + transitionProperty: "background-color", 74 + transitionTimingFunction: "ease-in-out", 75 + }, 76 + sidebarItemActive: { 77 + backgroundColor: { 78 + default: primaryColor.component1, 79 + ":hover": primaryColor.component2, 80 + ":active": primaryColor.component3, 81 + }, 82 + color: primaryColor.text2, 83 + }, 84 + sidebarGroupHeading: { 85 + margin: 0, 86 + }, 87 + sidebarGroup: { 88 + display: "flex", 89 + flexDirection: "column", 90 + }, 91 + sidebarGroupButton: { 92 + alignItems: "center", 93 + backgroundColor: "transparent", 94 + borderWidth: 0, 95 + color: uiColor.text2, 96 + display: "flex", 97 + fontFamily: fontFamily["sans"], 98 + fontSize: fontSize["base"], 99 + fontWeight: fontWeight["medium"], 100 + gap: spacing["1.5"], 101 + marginLeft: `calc(${spacing["2.5"]} * -1)`, 102 + padding: 0, 103 + textAlign: "left", 104 + width: "100%", 105 + }, 106 + chevronIcon: { 107 + rotate: { 108 + default: "0deg", 109 + ":is([aria-expanded=true] *)": "90deg", 110 + }, 111 + transition: "rotate 250ms", 112 + }, 113 + sidebarGroupPanel: { 114 + height: "var(--disclosure-panel-height)", 115 + overflow: "clip", 116 + transition: { 117 + default: "height 250ms", 118 + [mediaQueries.reducedMotion]: "none", 119 + }, 120 + }, 121 + sidebarGroupPanelContent: { 122 + display: "flex", 123 + flexDirection: "column", 124 + gap: spacing["4"], 125 + paddingTop: spacing["5"], 126 + }, 127 + }); 128 + 129 + export interface SidebarProps 130 + extends StyleXComponentProps<React.ComponentProps<"div">> { 131 + children: React.ReactNode; 132 + } 133 + 134 + export function Sidebar({ children, style, ...props }: SidebarProps) { 135 + const headerId = useId(); 136 + const contextValue = useMemo(() => ({ headerId }), [headerId]); 137 + 138 + return ( 139 + <SidebarContext value={contextValue}> 140 + <div 141 + {...props} 142 + {...stylex.props(styles.sidebar, style)} 143 + aria-labelledby={headerId} 144 + > 145 + {children} 146 + </div> 147 + </SidebarContext> 148 + ); 149 + } 150 + 151 + export interface SidebarHeaderProps 152 + extends StyleXComponentProps<React.ComponentProps<"div">> { 153 + children: React.ReactNode; 154 + href?: string; 155 + linkComponent?: React.ComponentType<React.ComponentProps<"a">>; 156 + } 157 + 158 + export function SidebarHeader({ 159 + children, 160 + style, 161 + href, 162 + linkComponent, 163 + ...props 164 + }: SidebarHeaderProps) { 165 + const { headerId } = use(SidebarContext); 166 + const LinkComponent = linkComponent || "a"; 167 + 168 + return ( 169 + <header 170 + {...props} 171 + {...stylex.props(styles.sidebarHeader, style)} 172 + id={headerId} 173 + > 174 + {href ? ( 175 + <LinkComponent 176 + href={href} 177 + {...stylex.props(styles.sidebarHeaderLink, style)} 178 + > 179 + {children} 180 + </LinkComponent> 181 + ) : ( 182 + children 183 + )} 184 + </header> 185 + ); 186 + } 187 + 188 + export interface SidebarGroupProps { 189 + children: React.ReactNode; 190 + title: string; 191 + defaultExpanded?: boolean; 192 + } 193 + 194 + export function SidebarGroup({ 195 + children, 196 + title, 197 + defaultExpanded = true, 198 + }: SidebarGroupProps) { 199 + return ( 200 + <Disclosure 201 + defaultExpanded={defaultExpanded} 202 + {...stylex.props(styles.sidebarGroup)} 203 + > 204 + <Heading {...stylex.props(styles.sidebarGroupHeading)}> 205 + <Button slot="trigger" {...stylex.props(styles.sidebarGroupButton)}> 206 + <ChevronRight size={16} {...stylex.props(styles.chevronIcon)} /> 207 + {title} 208 + </Button> 209 + </Heading> 210 + <DisclosurePanel {...stylex.props(styles.sidebarGroupPanel)}> 211 + <div {...stylex.props(styles.sidebarGroupPanelContent)}>{children}</div> 212 + </DisclosurePanel> 213 + </Disclosure> 214 + ); 215 + } 216 + 217 + export interface SidebarSectionProps { 218 + children: React.ReactNode; 219 + title?: string; 220 + } 221 + 222 + export function SidebarSection({ children, title }: SidebarSectionProps) { 223 + const headerId = useId(); 224 + 225 + return ( 226 + <Flex direction="column" gap="1"> 227 + {title && ( 228 + <div {...stylex.props(styles.sidebarSectionTitle)}> 229 + <Text id={headerId} size="sm" weight="medium" variant="secondary"> 230 + {title} 231 + </Text> 232 + </div> 233 + )} 234 + <ul 235 + aria-labelledby={title ? headerId : undefined} 236 + {...stylex.props(styles.sidebarSectionList)} 237 + > 238 + {children} 239 + </ul> 240 + </Flex> 241 + ); 242 + } 243 + 244 + export interface SidebarItemProps 245 + extends StyleXComponentProps<React.ComponentProps<"li">> { 246 + children: React.ReactNode; 247 + isActive?: boolean; 248 + } 249 + 250 + export function SidebarItem({ 251 + children, 252 + style, 253 + isActive, 254 + ...props 255 + }: SidebarItemProps) { 256 + return ( 257 + <li 258 + {...props} 259 + {...stylex.props( 260 + styles.sidebarItem, 261 + isActive && styles.sidebarItemActive, 262 + style, 263 + )} 264 + > 265 + {children} 266 + </li> 267 + ); 268 + }
+53
apps/docs/src/docs/components/sidebar.mdx
··· 1 + --- 2 + title: Sidebar 3 + description: A sidebar navigation component with support for sections, groups, and collapsible sections. 4 + --- 5 + 6 + import { PropDocs } from '../../lib/PropDocs' 7 + import { Example } from '../../lib/Example' 8 + import { Basic } from '../../examples/sidebar/basic' 9 + import { WithSections } from '../../examples/sidebar/with-sections' 10 + import { WithGroups } from '../../examples/sidebar/with-groups' 11 + 12 + <Example src={Basic} /> 13 + 14 + ## Installation 15 + 16 + Run the following command to add the sidebar component to your project. 17 + 18 + ```bash 19 + pnpm hip install sidebar 20 + ``` 21 + 22 + ## Props 23 + 24 + <PropDocs components={["Sidebar", "SidebarHeader", "SidebarGroup", "SidebarSection", "SidebarItem"]} /> 25 + 26 + ## Features 27 + 28 + ### With Sections 29 + 30 + Organize sidebar items into sections with titles. 31 + 32 + <Example src={WithSections} /> 33 + 34 + ### With Groups 35 + 36 + Create collapsible groups for organizing content hierarchically. 37 + 38 + <Example src={WithGroups} /> 39 + 40 + ## Components 41 + 42 + - `Sidebar` - The main container component 43 + - `SidebarHeader` - Header section, can be a link 44 + - `SidebarGroup` - Collapsible group with disclosure panel 45 + - `SidebarSection` - Non-collapsible section with title 46 + - `SidebarItem` - Individual navigation items 47 + 48 + ## Related Components 49 + 50 + - [Link](/docs/components/link) - For navigation links 51 + - [Button](/docs/components/button) - Used in SidebarGroup 52 + - [Flex](/docs/components/flex) - For layout 53 +
+63
apps/docs/src/examples/sidebar/basic.tsx
··· 1 + import * as stylex from "@stylexjs/stylex"; 2 + 3 + import { 4 + Sidebar, 5 + SidebarGroup, 6 + SidebarHeader, 7 + SidebarItem, 8 + SidebarSection, 9 + } from "@/components/sidebar"; 10 + 11 + import { uiColor } from "../../components/theme/semantic-color.stylex"; 12 + 13 + const styles = stylex.create({ 14 + wrapper: { 15 + backgroundColor: uiColor.bg, 16 + boxSizing: "border-box", 17 + height: 400, 18 + width: "100%", 19 + }, 20 + sidebar: { 21 + backgroundColor: uiColor.bgSubtle, 22 + borderRightColor: uiColor.border2, 23 + borderRightStyle: "solid", 24 + borderRightWidth: 1, 25 + boxSizing: "border-box", 26 + height: "100%", 27 + overflow: "auto", 28 + }, 29 + }); 30 + 31 + export function Basic() { 32 + return ( 33 + <div {...stylex.props(styles.wrapper)}> 34 + <Sidebar style={styles.sidebar}> 35 + <SidebarHeader>My App</SidebarHeader> 36 + <SidebarSection> 37 + <SidebarItem isActive>Dashboard</SidebarItem> 38 + <SidebarItem>Projects </SidebarItem> 39 + <SidebarItem>Settings </SidebarItem> 40 + </SidebarSection> 41 + 42 + <SidebarSection title="Settings"> 43 + <SidebarItem>General</SidebarItem> 44 + <SidebarItem>Team</SidebarItem> 45 + <SidebarItem>Billing</SidebarItem> 46 + </SidebarSection> 47 + 48 + <SidebarGroup title="Reports"> 49 + <SidebarSection title="Analytics"> 50 + <SidebarItem>Overview</SidebarItem> 51 + <SidebarItem>Sales</SidebarItem> 52 + <SidebarItem>Customers</SidebarItem> 53 + </SidebarSection> 54 + <SidebarSection title="Teams"> 55 + <SidebarItem>Team 1</SidebarItem> 56 + <SidebarItem>Team 2</SidebarItem> 57 + <SidebarItem>Team 3</SidebarItem> 58 + </SidebarSection> 59 + </SidebarGroup> 60 + </Sidebar> 61 + </div> 62 + ); 63 + }
+33
apps/docs/src/examples/sidebar/with-groups.tsx
··· 1 + import { 2 + Sidebar, 3 + SidebarGroup, 4 + SidebarHeader, 5 + SidebarItem, 6 + } from "@/components/sidebar"; 7 + import { Link } from "@/components/link"; 8 + 9 + export function WithGroups() { 10 + return ( 11 + <Sidebar> 12 + <SidebarHeader href="/"> 13 + <Link>My App</Link> 14 + </SidebarHeader> 15 + <SidebarGroup title="Navigation"> 16 + <SidebarItem> 17 + <Link href="/dashboard">Dashboard</Link> 18 + </SidebarItem> 19 + <SidebarItem> 20 + <Link href="/analytics">Analytics</Link> 21 + </SidebarItem> 22 + </SidebarGroup> 23 + <SidebarGroup title="Settings"> 24 + <SidebarItem> 25 + <Link href="/settings">General</Link> 26 + </SidebarItem> 27 + <SidebarItem> 28 + <Link href="/settings/team">Team</Link> 29 + </SidebarItem> 30 + </SidebarGroup> 31 + </Sidebar> 32 + ); 33 + }
+39
apps/docs/src/examples/sidebar/with-sections.tsx
··· 1 + import { 2 + Sidebar, 3 + SidebarHeader, 4 + SidebarItem, 5 + SidebarSection, 6 + } from "@/components/sidebar"; 7 + import { Link } from "@/components/link"; 8 + 9 + export function WithSections() { 10 + return ( 11 + <Sidebar> 12 + <SidebarHeader href="/"> 13 + <Link>My App</Link> 14 + </SidebarHeader> 15 + <SidebarSection title="Navigation"> 16 + <SidebarItem> 17 + <Link href="/dashboard">Dashboard</Link> 18 + </SidebarItem> 19 + <SidebarItem> 20 + <Link href="/analytics">Analytics</Link> 21 + </SidebarItem> 22 + <SidebarItem> 23 + <Link href="/reports">Reports</Link> 24 + </SidebarItem> 25 + </SidebarSection> 26 + <SidebarSection title="Settings"> 27 + <SidebarItem> 28 + <Link href="/settings">General</Link> 29 + </SidebarItem> 30 + <SidebarItem> 31 + <Link href="/settings/team">Team</Link> 32 + </SidebarItem> 33 + <SidebarItem> 34 + <Link href="/settings/billing">Billing</Link> 35 + </SidebarItem> 36 + </SidebarSection> 37 + </Sidebar> 38 + ); 39 + }
-2
apps/docs/src/routes/docs.$.tsx
··· 22 22 import { createContext, use, useEffect, useRef, useState } from "react"; 23 23 import { pages, tableOfContents } from "virtual:content"; 24 24 25 - import { Button } from "@/components/button"; 26 25 import { Flex } from "@/components/flex"; 27 26 import { Grid } from "@/components/grid"; 28 27 import { LinkProps, Link as TypographyLink } from "@/components/link"; ··· 299 298 } 300 299 301 300 const toc = tableOfContents[location.pathname]; 302 - console.log(toc); 303 301 304 302 return ( 305 303 <Grid columns="max-content 240px" columnGap="4" style={styles.root}>
+45 -30
apps/docs/src/routes/docs.tsx
··· 8 8 createLink, 9 9 } from "@tanstack/react-router"; 10 10 import { allDocs } from "content-collections"; 11 - import { Collection } from "react-aria-components"; 12 11 13 12 import { Grid } from "@/components/grid"; 14 - import { Tree, TreeItem } from "@/components/tree"; 13 + import { 14 + Sidebar, 15 + SidebarGroup, 16 + SidebarHeader, 17 + SidebarItem, 18 + SidebarSection, 19 + } from "@/components/sidebar"; 20 + import { Text } from "@/components/typography/text"; 15 21 16 - import { spacing } from "../components/theme/spacing.stylex"; 22 + import { uiColor } from "../components/theme/semantic-color.stylex"; 17 23 18 - const TreeItemLink = createLink(TreeItem); 24 + const SidebarItemLink = createLink(SidebarItem); 19 25 20 26 const styles = stylex.create({ 21 27 root: { 22 28 width: "100%", 23 29 }, 24 30 aside: { 31 + backgroundColor: uiColor.bgSubtle, 32 + borderRightColor: uiColor.border2, 33 + borderRightStyle: "solid", 34 + borderRightWidth: 1, 25 35 boxSizing: "border-box", 26 36 height: "100vh", 27 37 overflow: "auto", 28 - paddingBottom: spacing["16"], 29 - paddingLeft: spacing["4"], 30 - paddingRight: spacing["4"], 31 - paddingTop: spacing["12"], 32 38 position: "sticky", 33 39 top: 0, 34 40 }, ··· 53 59 ); 54 60 55 61 const sidebarItems: SidebarItem[] = [ 56 - { 57 - id: "home", 58 - label: "Home", 59 - to: "/", 60 - }, 61 62 { 62 63 id: "foundations", 63 64 label: "Foundations", ··· 94 95 .flatMap((item) => ("items" in item ? item.items : [item])) 95 96 .filter((item): item is SidebarItem => item !== undefined); 96 97 97 - function Sidebar() { 98 + function DocSidebar() { 98 99 const location = useLocation(); 99 100 const matches = useMatches(); 100 101 const match = matches.find((match) => match.pathname === location.pathname); 101 - const item = flatItems.find( 102 + const currentItem = flatItems.find( 102 103 (item) => 103 104 match?.params && 104 105 "_splat" in match.params && ··· 107 108 ); 108 109 109 110 return ( 110 - <Tree 111 - items={sidebarItems} 112 - selectionMode="single" 113 - selectionBehavior="replace" 114 - defaultExpandedKeys={["foundations", "components"]} 115 - selectedKeys={item ? [item.id] : []} 116 - > 117 - {function renderTreeItem(item) { 111 + <Sidebar> 112 + <SidebarHeader> 113 + <Text font="serif" size="4xl" weight="bold"> 114 + Hip UI 115 + </Text> 116 + </SidebarHeader> 117 + {sidebarItems.map((item) => { 118 + if (!item.items) { 119 + return null; 120 + } 121 + 118 122 return ( 119 - <TreeItemLink title={item.label} to={item.to} params={item.params}> 120 - <Collection items={item.items}>{renderTreeItem}</Collection> 121 - </TreeItemLink> 123 + <SidebarGroup title={item.label} key={item.id}> 124 + <SidebarSection> 125 + {item.items.map((item) => ( 126 + <SidebarItemLink 127 + key={item.id} 128 + to={item.to} 129 + params={item.params} 130 + isActive={currentItem?.id === item.id} 131 + > 132 + {item.label} 133 + </SidebarItemLink> 134 + ))} 135 + </SidebarSection> 136 + </SidebarGroup> 122 137 ); 123 - }} 124 - </Tree> 138 + })} 139 + </Sidebar> 125 140 ); 126 141 } 127 142 ··· 131 146 132 147 function RouteComponent() { 133 148 return ( 134 - <Grid columns="240px 1fr" columnGap="4" style={styles.root}> 149 + <Grid columns="max-content 1fr" columnGap="4" style={styles.root}> 135 150 <aside {...stylex.props(styles.aside)}> 136 - <Sidebar /> 151 + <DocSidebar /> 137 152 </aside> 138 153 <main> 139 154 <Outlet />
+2
packages/hip-ui/src/cli/install.tsx
··· 43 43 import { segmentedControlConfig } from "../components/segmented-control/segmented-control-config.js"; 44 44 import { selectConfig } from "../components/select/select-config.js"; 45 45 import { separatorConfig } from "../components/separator/separator-config.js"; 46 + import { sidebarConfig } from "../components/sidebar/sidebar-config.js"; 46 47 import { sliderConfig } from "../components/slider/slider-config.js"; 47 48 import { switchConfig } from "../components/switch/switch-config.js"; 48 49 import { tableConfig } from "../components/table/table-config.js"; ··· 108 109 segmentedControlConfig, 109 110 paginationConfig, 110 111 kbdConfig, 112 + sidebarConfig, 111 113 ]; 112 114 113 115 function StringSetting({
+4 -1
packages/hip-ui/src/components/listbox/index.tsx
··· 122 122 <div {...stylex.props(listBoxItemStyles.addon)}> 123 123 <ListBoxCheckbox 124 124 isSelected={isSelected} 125 - id={props.id || (props.value as { id: string })?.id} 125 + id={ 126 + (props.id as string) || 127 + (props.value as { id?: string } | undefined)?.id 128 + } 126 129 onPress={(e) => e.continuePropagation()} 127 130 /> 128 131 </div>
+268
packages/hip-ui/src/components/sidebar/index.tsx
··· 1 + import * as stylex from "@stylexjs/stylex"; 2 + import { ChevronRight } from "lucide-react"; 3 + import { createContext, use, useId, useMemo } from "react"; 4 + import { 5 + Button, 6 + Disclosure, 7 + DisclosurePanel, 8 + Heading, 9 + } from "react-aria-components"; 10 + 11 + import { Flex } from "../flex"; 12 + import { mediaQueries } from "../theme/media-queries.stylex"; 13 + import { radius } from "../theme/radius.stylex"; 14 + import { primaryColor, uiColor } from "../theme/semantic-color.stylex"; 15 + import { spacing } from "../theme/spacing.stylex"; 16 + import { StyleXComponentProps } from "../theme/types"; 17 + import { fontFamily, fontSize, fontWeight } from "../theme/typography.stylex"; 18 + import { Text } from "../typography/text"; 19 + 20 + interface SidebarContextType { 21 + headerId: string; 22 + } 23 + 24 + const SidebarContext = createContext<SidebarContextType>({ 25 + headerId: "", 26 + }); 27 + 28 + const styles = stylex.create({ 29 + sidebar: { 30 + display: "flex", 31 + flexDirection: "column", 32 + gap: spacing["6"], 33 + paddingBottom: spacing["12"], 34 + paddingLeft: spacing["8"], 35 + paddingRight: spacing["8"], 36 + paddingTop: spacing["6"], 37 + width: spacing["64"], 38 + }, 39 + sidebarHeader: { 40 + marginBottom: spacing["4"], 41 + padding: spacing["3"], 42 + }, 43 + sidebarHeaderLink: { 44 + textDecoration: "none", 45 + }, 46 + sidebarSectionTitle: { 47 + height: spacing["6"], 48 + paddingLeft: spacing["3"], 49 + paddingRight: spacing["3"], 50 + }, 51 + sidebarSectionList: { 52 + display: "flex", 53 + flexDirection: "column", 54 + gap: spacing["1"], 55 + margin: 0, 56 + padding: 0, 57 + }, 58 + sidebarItem: { 59 + alignItems: "center", 60 + backgroundColor: { 61 + default: "transparent", 62 + ":hover": uiColor.component2, 63 + ":active": uiColor.component3, 64 + }, 65 + borderRadius: radius["md"], 66 + color: uiColor.text2, 67 + display: "flex", 68 + height: spacing["8"], 69 + listStyle: "none", 70 + paddingLeft: spacing["3"], 71 + paddingRight: spacing["3"], 72 + transitionDuration: "100ms", 73 + transitionProperty: "background-color", 74 + transitionTimingFunction: "ease-in-out", 75 + }, 76 + sidebarItemActive: { 77 + backgroundColor: { 78 + default: primaryColor.component1, 79 + ":hover": primaryColor.component2, 80 + ":active": primaryColor.component3, 81 + }, 82 + color: primaryColor.text2, 83 + }, 84 + sidebarGroupHeading: { 85 + margin: 0, 86 + }, 87 + sidebarGroup: { 88 + display: "flex", 89 + flexDirection: "column", 90 + }, 91 + sidebarGroupButton: { 92 + alignItems: "center", 93 + backgroundColor: "transparent", 94 + borderWidth: 0, 95 + color: uiColor.text2, 96 + display: "flex", 97 + fontFamily: fontFamily["sans"], 98 + fontSize: fontSize["base"], 99 + fontWeight: fontWeight["medium"], 100 + gap: spacing["1.5"], 101 + marginLeft: `calc(${spacing["2.5"]} * -1)`, 102 + padding: 0, 103 + textAlign: "left", 104 + width: "100%", 105 + }, 106 + chevronIcon: { 107 + rotate: { 108 + default: "0deg", 109 + ":is([aria-expanded=true] *)": "90deg", 110 + }, 111 + transition: "rotate 250ms", 112 + }, 113 + sidebarGroupPanel: { 114 + height: "var(--disclosure-panel-height)", 115 + overflow: "clip", 116 + transition: { 117 + default: "height 250ms", 118 + [mediaQueries.reducedMotion]: "none", 119 + }, 120 + }, 121 + sidebarGroupPanelContent: { 122 + display: "flex", 123 + flexDirection: "column", 124 + gap: spacing["4"], 125 + paddingTop: spacing["5"], 126 + }, 127 + }); 128 + 129 + export interface SidebarProps 130 + extends StyleXComponentProps<React.ComponentProps<"div">> { 131 + children: React.ReactNode; 132 + } 133 + 134 + export function Sidebar({ children, style, ...props }: SidebarProps) { 135 + const headerId = useId(); 136 + const contextValue = useMemo(() => ({ headerId }), [headerId]); 137 + 138 + return ( 139 + <SidebarContext value={contextValue}> 140 + <div 141 + {...props} 142 + {...stylex.props(styles.sidebar, style)} 143 + aria-labelledby={headerId} 144 + > 145 + {children} 146 + </div> 147 + </SidebarContext> 148 + ); 149 + } 150 + 151 + export interface SidebarHeaderProps 152 + extends StyleXComponentProps<React.ComponentProps<"div">> { 153 + children: React.ReactNode; 154 + href?: string; 155 + linkComponent?: React.ComponentType<React.ComponentProps<"a">>; 156 + } 157 + 158 + export function SidebarHeader({ 159 + children, 160 + style, 161 + href, 162 + linkComponent, 163 + ...props 164 + }: SidebarHeaderProps) { 165 + const { headerId } = use(SidebarContext); 166 + const LinkComponent = linkComponent || "a"; 167 + 168 + return ( 169 + <header 170 + {...props} 171 + {...stylex.props(styles.sidebarHeader, style)} 172 + id={headerId} 173 + > 174 + {href ? ( 175 + <LinkComponent 176 + href={href} 177 + {...stylex.props(styles.sidebarHeaderLink, style)} 178 + > 179 + {children} 180 + </LinkComponent> 181 + ) : ( 182 + children 183 + )} 184 + </header> 185 + ); 186 + } 187 + 188 + export interface SidebarGroupProps { 189 + children: React.ReactNode; 190 + title: string; 191 + defaultExpanded?: boolean; 192 + } 193 + 194 + export function SidebarGroup({ 195 + children, 196 + title, 197 + defaultExpanded = true, 198 + }: SidebarGroupProps) { 199 + return ( 200 + <Disclosure 201 + defaultExpanded={defaultExpanded} 202 + {...stylex.props(styles.sidebarGroup)} 203 + > 204 + <Heading {...stylex.props(styles.sidebarGroupHeading)}> 205 + <Button slot="trigger" {...stylex.props(styles.sidebarGroupButton)}> 206 + <ChevronRight size={16} {...stylex.props(styles.chevronIcon)} /> 207 + {title} 208 + </Button> 209 + </Heading> 210 + <DisclosurePanel {...stylex.props(styles.sidebarGroupPanel)}> 211 + <div {...stylex.props(styles.sidebarGroupPanelContent)}>{children}</div> 212 + </DisclosurePanel> 213 + </Disclosure> 214 + ); 215 + } 216 + 217 + export interface SidebarSectionProps { 218 + children: React.ReactNode; 219 + title?: string; 220 + } 221 + 222 + export function SidebarSection({ children, title }: SidebarSectionProps) { 223 + const headerId = useId(); 224 + 225 + return ( 226 + <Flex direction="column" gap="1"> 227 + {title && ( 228 + <div {...stylex.props(styles.sidebarSectionTitle)}> 229 + <Text id={headerId} size="sm" weight="medium" variant="secondary"> 230 + {title} 231 + </Text> 232 + </div> 233 + )} 234 + <ul 235 + aria-labelledby={title ? headerId : undefined} 236 + {...stylex.props(styles.sidebarSectionList)} 237 + > 238 + {children} 239 + </ul> 240 + </Flex> 241 + ); 242 + } 243 + 244 + export interface SidebarItemProps 245 + extends StyleXComponentProps<React.ComponentProps<"li">> { 246 + children: React.ReactNode; 247 + isActive?: boolean; 248 + } 249 + 250 + export function SidebarItem({ 251 + children, 252 + style, 253 + isActive, 254 + ...props 255 + }: SidebarItemProps) { 256 + return ( 257 + <li 258 + {...props} 259 + {...stylex.props( 260 + styles.sidebarItem, 261 + isActive && styles.sidebarItemActive, 262 + style, 263 + )} 264 + > 265 + {children} 266 + </li> 267 + ); 268 + }
+6
packages/hip-ui/src/components/sidebar/sidebar-config.ts
··· 1 + import { ComponentConfig } from "../../types"; 2 + 3 + export const sidebarConfig: ComponentConfig = { 4 + name: "sidebar", 5 + filepath: "./index.tsx", 6 + };