{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "settings-dialog",
  "title": "Settings Dialog",
  "description": "A dialog laid out as a nav beside a page, for settings with several pages.",
  "dependencies": [
    "@stylexjs/stylex"
  ],
  "registryDependencies": [
    "https://constella.aayush.cv/r/base.json",
    "https://constella.aayush.cv/r/dialog.json",
    "https://constella.aayush.cv/r/sidebar.json"
  ],
  "files": [
    {
      "path": "src/components/primitives/settings-dialog.tsx",
      "content": "\"use client\";\n\nimport * as stylex from \"@stylexjs/stylex\";\nimport type { ComponentProps, ReactNode } from \"react\";\n\nimport {\n  Dialog,\n  DialogCloseButton,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n} from \"@/components/primitives/dialog\";\nimport {\n  Sidebar,\n  SidebarContent,\n  SidebarGroup,\n  SidebarGroupLabel,\n  SidebarMenu,\n  SidebarProvider,\n} from \"@/components/primitives/sidebar\";\nimport type { Sx } from \"@/lib/constella/styles\";\nimport { typeStyles } from \"@/lib/constella/styles\";\nimport { media, sizes, space } from \"@/lib/constella/tokens.stylex\";\n\ntype Styled<T> = Omit<T, \"className\" | \"style\"> & {\n  sx?: Sx;\n};\n\nconst styles = stylex.create({\n  // Narrow, the nav stacks above the page.\n  popup: {\n    flexDirection: { default: \"column\", [media.sm]: \"row\" },\n    gap: 0,\n    height: sizes.settingsDialogHeight,\n    overflowX: \"hidden\",\n    overflowY: \"hidden\",\n    paddingBlockEnd: 0,\n    paddingBlockStart: 0,\n    paddingInlineEnd: 0,\n    paddingInlineStart: 0,\n    width: `min(100%, ${sizes.settingsDialog})`,\n  },\n  // The provider fills the window beside an app; here it only holds the nav.\n  provider: {\n    flexShrink: 0,\n    minHeight: 0,\n    width: \"auto\",\n  },\n  // Stacked, the nav runs under the close button, so its rows end before it.\n  nav: {\n    height: { default: \"auto\", [media.sm]: \"100%\" },\n    paddingInlineEnd: {\n      default: `calc(${sizes.controlSm} + ${space.xs})`,\n      [media.sm]: 0,\n    },\n    width: { default: \"100%\", [media.sm]: sizes.sidebar },\n  },\n  // Inside the scroller, so the first row's focus ring isn't clipped.\n  navContent: {\n    paddingBlockStart: space.md,\n  },\n  // The one pane that scrolls on its own: the page behind a dialog can't.\n  page: {\n    display: \"flex\",\n    flexDirection: \"column\",\n    flexGrow: 1,\n    gap: space.md,\n    minHeight: 0,\n    minWidth: 0,\n    overflowY: \"auto\",\n    paddingBlockEnd: space.lg,\n    paddingBlockStart: space.lg,\n    paddingInlineEnd: space.lg,\n    paddingInlineStart: space.lg,\n  },\n  // Beside the nav, the close button drops to the page's padding, onto its title line.\n  close: {\n    insetBlockStart: { default: space.xs, [media.sm]: space.lg },\n    insetInlineEnd: { default: space.xs, [media.sm]: space.lg },\n  },\n  // Ends its controls clear of the close button on the same line.\n  header: {\n    paddingInlineEnd: {\n      default: `calc(${sizes.controlSm} + ${space.xs} * 2 - ${space.lg})`,\n      [media.sm]: `calc(${sizes.controlSm} + ${space.lg})`,\n    },\n  },\n  titleLine: {\n    alignItems: \"center\",\n    display: \"flex\",\n    gap: space.sm,\n    minHeight: sizes.controlSm,\n  },\n  title: {\n    flexGrow: 1,\n    minWidth: 0,\n  },\n  description: {\n    textWrap: \"balance\",\n  },\n});\n\n/** A dialog laid out as a nav beside a page, with a close button in its corner. */\nfunction SettingsDialog({\n  children,\n  sx,\n  ...props\n}: Omit<ComponentProps<typeof Dialog>, \"children\"> & {\n  children?: ReactNode;\n  sx?: Sx;\n}) {\n  return (\n    <Dialog {...props}>\n      <DialogContent data-slot=\"settings-dialog\" sx={[styles.popup, sx]}>\n        {children}\n        <DialogCloseButton sx={styles.close} />\n      </DialogContent>\n    </Dialog>\n  );\n}\n\n/** Holds a `SidebarMenuItem` per page, under an optional group `title`. */\nfunction SettingsDialogNav({\n  children,\n  label,\n  sx,\n  title,\n  ...props\n}: Styled<Omit<ComponentProps<\"div\">, \"ref\" | \"title\">> & {\n  label: string;\n  title?: ReactNode;\n}) {\n  return (\n    <SidebarProvider sx={styles.provider}>\n      <Sidebar collapsible=\"none\" {...props} sx={[styles.nav, sx]}>\n        <SidebarContent sx={styles.navContent}>\n          <SidebarGroup>\n            {title === undefined ? null : (\n              <SidebarGroupLabel>{title}</SidebarGroupLabel>\n            )}\n            <nav aria-label={label}>\n              <SidebarMenu>{children}</SidebarMenu>\n            </nav>\n          </SidebarGroup>\n        </SidebarContent>\n      </Sidebar>\n    </SidebarProvider>\n  );\n}\n\nfunction SettingsDialogPage({\n  action,\n  children,\n  description,\n  leading,\n  sx,\n  title,\n  ...props\n}: Styled<Omit<ComponentProps<\"div\">, \"title\">> & {\n  action?: ReactNode;\n  description?: ReactNode;\n  leading?: ReactNode;\n  title: ReactNode;\n}) {\n  return (\n    <div\n      data-slot=\"settings-dialog-page\"\n      {...props}\n      {...stylex.props(styles.page, sx)}\n    >\n      <DialogHeader sx={styles.header}>\n        <div {...stylex.props(styles.titleLine)}>\n          {leading}\n          <DialogTitle sx={[typeStyles.textLg, styles.title]}>\n            {title}\n          </DialogTitle>\n          {action}\n        </div>\n        {description === undefined ? null : (\n          <DialogDescription sx={styles.description}>\n            {description}\n          </DialogDescription>\n        )}\n      </DialogHeader>\n      {children}\n    </div>\n  );\n}\n\nexport { SettingsDialog, SettingsDialogNav, SettingsDialogPage };\n",
      "type": "registry:component"
    }
  ],
  "docs": "Usage: https://constella.aayush.cv/docs/components/settings-dialog/index.md",
  "type": "registry:ui"
}