diff --git a/apps/web/project.json b/apps/web/project.json
index a87f298..76b76fe 100644
--- a/apps/web/project.json
+++ b/apps/web/project.json
@@ -19,6 +19,7 @@
"styles": ["apps/web/src/styles.css"],
"scripts": [],
"isolatedConfig": true,
+ "postcssConfig": "apps/web/postcss.config.cjs",
"webpackConfig": "apps/web/webpack.config.js"
},
"configurations": {
diff --git a/apps/web/src/app/app-layout.tsx b/apps/web/src/app/app-layout.tsx
index f8f448e..d9af079 100644
--- a/apps/web/src/app/app-layout.tsx
+++ b/apps/web/src/app/app-layout.tsx
@@ -1,57 +1,56 @@
-import { ActionIcon, Anchor, Box, Card, Group } from '@mantine/core'
-import { UiContainer, UiLogoType, useUiColorScheme, useUiTheme } from '@pubkey-ui/core'
-import { IconMoon, IconSun } from '@tabler/icons-react'
+import { Avatar, Group, rem } from '@mantine/core'
+import { UiHeader, UiLayout, UiMenu, UiThemeSwitch } from '@pubkey-ui/core'
+import { IconSettings, IconUser, IconUserCog } from '@tabler/icons-react'
import { ReactNode } from 'react'
import { AccountChecker } from './features/account/account-ui'
-import { ClusterChecker, ClusterUiSelect } from './features/cluster/cluster-ui'
+import { ClusterChecker } from './features/cluster/cluster-ui'
export function AppLayout({ children }: { children: ReactNode }) {
- const { Link } = useUiTheme()
return (
-
-
-
-
-
-
-
-
-
- Dashboard
-
-
- Account
-
-
- Demo
-
-
- Dev
-
-
+
-
-
+
+
+
+
+ }
+ items={[
+ { label: 'Account', type: 'label' },
+ {
+ label: 'Settings',
+ type: 'item',
+ leftSection: ,
+ },
+ {
+ label: 'Profile',
+ type: 'item',
+ leftSection: ,
+ },
+ ]}
+ />
-
-
-
+ }
+ />
+ }
+ >
{children}
-
- )
-}
-
-function ThemeToggle() {
- const { toggleColorScheme, colorScheme } = useUiColorScheme()
-
- return (
-
- toggleColorScheme()} variant="default" size="xl" aria-label="Toggle color scheme">
- {colorScheme === 'dark' ? : }
-
-
+
)
}
diff --git a/apps/web/src/app/app.tsx b/apps/web/src/app/app.tsx
index 1501baf..02b0dc1 100644
--- a/apps/web/src/app/app.tsx
+++ b/apps/web/src/app/app.tsx
@@ -1,9 +1,9 @@
import { UiThemeProvider } from '@pubkey-ui/core'
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { AppLayout } from './app-layout'
import { AppRoutes, ThemeLink } from './app-routes'
import { ClusterProvider } from './features/cluster/cluster-data-access'
import { SolanaProvider } from './features/solana/solana-provider'
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
const client = new QueryClient()
diff --git a/apps/web/src/app/features/account/account-ui.tsx b/apps/web/src/app/features/account/account-ui.tsx
index a3e05e8..9feaa79 100644
--- a/apps/web/src/app/features/account/account-ui.tsx
+++ b/apps/web/src/app/features/account/account-ui.tsx
@@ -57,6 +57,10 @@ export function AccountBalanceCheck({ address }: { address: PublicKey }) {
if (query.isError || !query.data) {
return (
}
message={
diff --git a/apps/web/src/app/features/cluster/cluster-ui.tsx b/apps/web/src/app/features/cluster/cluster-ui.tsx
index bd4f3b4..24b54b7 100644
--- a/apps/web/src/app/features/cluster/cluster-ui.tsx
+++ b/apps/web/src/app/features/cluster/cluster-ui.tsx
@@ -59,6 +59,10 @@ export function ClusterChecker({ children }: { children: ReactNode }) {
if (query.isError || !query.data) {
return (
}
message={
diff --git a/apps/web/src/app/features/demo/demo-feature-alerts.tsx b/apps/web/src/app/features/demo/demo-feature-alerts.tsx
index 59819b1..0c072b9 100644
--- a/apps/web/src/app/features/demo/demo-feature-alerts.tsx
+++ b/apps/web/src/app/features/demo/demo-feature-alerts.tsx
@@ -1,5 +1,5 @@
import { SimpleGrid } from '@mantine/core'
-import { UiCard, UiAlert, UiError, UiInfo, UiSuccess, UiWarning } from '@pubkey-ui/core'
+import { UiAlert, UiCard, UiError, UiInfo, UiSuccess, UiWarning } from '@pubkey-ui/core'
export function DemoFeatureAlerts() {
return (
diff --git a/apps/web/src/app/features/demo/demo-feature-card.tsx b/apps/web/src/app/features/demo/demo-feature-card.tsx
index da877c5..bc0c8f8 100644
--- a/apps/web/src/app/features/demo/demo-feature-card.tsx
+++ b/apps/web/src/app/features/demo/demo-feature-card.tsx
@@ -3,6 +3,7 @@ import { UiCard, UiStack } from '@pubkey-ui/core'
export function DemoFeatureCard() {
return (
+ CARD CONTENT
CARD CONTENT
diff --git a/apps/web/src/app/features/demo/demo-feature-header.tsx b/apps/web/src/app/features/demo/demo-feature-header.tsx
new file mode 100644
index 0000000..6bb4edf
--- /dev/null
+++ b/apps/web/src/app/features/demo/demo-feature-header.tsx
@@ -0,0 +1,136 @@
+import { ActionIcon, Avatar, Button, Group, rem, Text } from '@mantine/core'
+import { useDisclosure } from '@mantine/hooks'
+import { UiCard, UiHeader, UiMenu, UiSearchInput, UiStack } from '@pubkey-ui/core'
+import {
+ IconDoorExit,
+ IconLetterP,
+ IconMessageCircle,
+ IconPhoto,
+ IconSearch,
+ IconSettings,
+ IconUser,
+ IconUserCog,
+} from '@tabler/icons-react'
+import { useState } from 'react'
+import { Route, Routes } from 'react-router-dom'
+
+export function DemoFeatureHeader() {
+ const [opened, { toggle }] = useDisclosure(false)
+ const [signedIn, setSignedIn] = useState(false)
+ return (
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ PubKey
+
+
+ }
+ logoSmall={}
+ />
+
+ {signedIn ? (
+
+
+
+ }
+ items={[
+ { label: 'Application', type: 'label' },
+ {
+ label: 'Settings',
+ type: 'item',
+ leftSection: ,
+ },
+ {
+ label: 'Messages',
+ type: 'item',
+ leftSection: ,
+ },
+ {
+ label: 'Gallery',
+ type: 'item',
+ leftSection: ,
+ },
+ {
+ label: 'Search',
+ type: 'item',
+ leftSection: ,
+ rightSection: (
+
+ ⌘K
+
+ ),
+ },
+ {
+ label: 'Account',
+ type: 'label',
+ },
+ {
+ label: 'Profile',
+ type: 'item',
+ leftSection: ,
+ },
+ {
+ label: 'Sign out',
+ type: 'item',
+ leftSection: ,
+ onClick: () => setSignedIn(false),
+ },
+ ]}
+ />
+ ) : (
+
+ )}
+
+ }
+ />
+
+ {opened ? (
+
+ ) : (
+
+ )}
+
+
+
+ About} />
+ Pricing} />
+ Learn} />
+
+
+
+ )
+}
diff --git a/apps/web/src/app/features/demo/demo-feature-logo.tsx b/apps/web/src/app/features/demo/demo-feature-logo.tsx
index fd93ad0..e415e21 100644
--- a/apps/web/src/app/features/demo/demo-feature-logo.tsx
+++ b/apps/web/src/app/features/demo/demo-feature-logo.tsx
@@ -1,13 +1,40 @@
import { SimpleGrid } from '@mantine/core'
-import { UiCard, UiLogoType, UiLogoTypeBlack, UiLogoTypeWhite } from '@pubkey-ui/core'
+import {
+ UiCard,
+ UiGroup,
+ UiLogo,
+ UiLogoType,
+ UiLogoTypeBlack,
+ UiLogoTypeWhite,
+ UiStack,
+ UiThemeSwitch,
+} from '@pubkey-ui/core'
export function DemoFeatureLogo() {
return (
-
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Toggle the theme to see the logo change color.
+
+
+
)
diff --git a/apps/web/src/app/features/demo/demo-feature-menu.tsx b/apps/web/src/app/features/demo/demo-feature-menu.tsx
new file mode 100644
index 0000000..35997b4
--- /dev/null
+++ b/apps/web/src/app/features/demo/demo-feature-menu.tsx
@@ -0,0 +1,84 @@
+import { Avatar, rem, Text } from '@mantine/core'
+import { UiCard, UiGroup, UiMenu, UiMenuItem, useUiColorScheme } from '@pubkey-ui/core'
+import {
+ IconDoorExit,
+ IconMessageCircle,
+ IconMoon,
+ IconPhoto,
+ IconSearch,
+ IconSettings,
+ IconSun,
+ IconUser,
+ IconUserCog,
+} from '@tabler/icons-react'
+import { ReactNode } from 'react'
+
+export function DemoFeatureMenu() {
+ const { colorScheme, toggleColorScheme } = useUiColorScheme()
+ const icon: ReactNode = (
+
+
+
+ )
+ const items: UiMenuItem[] = [
+ { label: 'Application', type: 'label' },
+ {
+ label: 'Settings',
+ type: 'item',
+ leftSection: ,
+ },
+ {
+ label: 'Messages',
+ type: 'item',
+ leftSection: ,
+ },
+ {
+ label: 'Gallery',
+ type: 'item',
+ leftSection: ,
+ },
+ {
+ label: 'Search',
+ type: 'item',
+ leftSection: ,
+ rightSection: (
+
+ ⌘K
+
+ ),
+ },
+ {
+ label: `${colorScheme === 'dark' ? 'Light' : 'Dark'} theme`,
+ type: 'item',
+ leftSection:
+ colorScheme === 'dark' ? (
+
+ ) : (
+
+ ),
+ onClick: () => toggleColorScheme(),
+ },
+ {
+ label: 'Account',
+ type: 'label',
+ },
+ {
+ label: 'Profile',
+ type: 'item',
+ leftSection: ,
+ },
+ {
+ label: 'Sign out',
+ type: 'item',
+ leftSection: ,
+ },
+ ]
+ return (
+
+
+
+
+
+
+ )
+}
diff --git a/apps/web/src/app/features/demo/demo-feature-search-input.tsx b/apps/web/src/app/features/demo/demo-feature-search-input.tsx
index e5abb46..0b4057d 100644
--- a/apps/web/src/app/features/demo/demo-feature-search-input.tsx
+++ b/apps/web/src/app/features/demo/demo-feature-search-input.tsx
@@ -1,22 +1,24 @@
import { SimpleGrid } from '@mantine/core'
-import { UiCard, UiGroup, UiSearchInput } from '@pubkey-ui/core'
+import { UiCard, UiGroup, UiSearchInput, UiStack } from '@pubkey-ui/core'
export function DemoFeatureSearchInput() {
return (
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
)
}
diff --git a/apps/web/src/app/features/demo/demo-feature-tab-routes.tsx b/apps/web/src/app/features/demo/demo-feature-tab-routes.tsx
index 53d12e2..8b2240f 100644
--- a/apps/web/src/app/features/demo/demo-feature-tab-routes.tsx
+++ b/apps/web/src/app/features/demo/demo-feature-tab-routes.tsx
@@ -3,9 +3,9 @@ import { UiCard, UiTabRoutes } from '@pubkey-ui/core'
export function DemoFeatureTabRoutes() {
return (
-
+
},
+ { path: 'card', label: 'Card', element: },
+ { path: 'copy', label: 'Copy', element: },
+ { path: 'debug', label: 'Debug', element: },
+ { path: 'group', label: 'Group', element: },
+ { path: 'header', label: 'Header', element: },
+ { path: 'logo', label: 'Logo', element: },
+ { path: 'menu', label: 'Menu', element: },
+ { path: 'search-input', label: 'Search Input', element: },
+ { path: 'stack', label: 'Stack', element: },
+ { path: 'tab-routes', label: 'Tab Routes', element: },
+ { path: 'time', label: 'Time', element: },
+ { path: 'toast', label: 'Toast', element: },
+ ]
+
+ const routes = useRoutes([
+ { index: true, element: },
+ ...demos.map((demo) => ({ path: `${demo.path}/*`, element: demo.element })),
+ ])
+
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ {demos.map((demo) => {
+ const to = `/demo/${demo.path}`
+ return (
+
+ )
+ })}
+
+
+ {routes}
+
+
)
}
diff --git a/packages/core/src/lib/index.ts b/packages/core/src/lib/index.ts
index b518ad2..650b073 100644
--- a/packages/core/src/lib/index.ts
+++ b/packages/core/src/lib/index.ts
@@ -4,10 +4,14 @@ export * from './ui-container'
export * from './ui-copy'
export * from './ui-debug'
export * from './ui-group'
+export * from './ui-header'
+export * from './ui-layout'
export * from './ui-logo'
+export * from './ui-menu'
export * from './ui-search-input'
export * from './ui-stack'
export * from './ui-tab-routes'
export * from './ui-theme'
+export * from './ui-theme-switch'
export * from './ui-time'
export * from './ui-toast'
diff --git a/packages/core/src/lib/ui-card/ui-card.tsx b/packages/core/src/lib/ui-card/ui-card.tsx
index b0e0776..a9c14a8 100644
--- a/packages/core/src/lib/ui-card/ui-card.tsx
+++ b/packages/core/src/lib/ui-card/ui-card.tsx
@@ -13,7 +13,7 @@ export function UiCard({ loading, title, ...props }: UiCardProps) {
const { isSm } = useUiBreakpoints()
return (
-
+
{title ? (
{typeof title === 'string' ? {title} : title}
) : null}
diff --git a/packages/core/src/lib/ui-header/index.ts b/packages/core/src/lib/ui-header/index.ts
new file mode 100644
index 0000000..7ef2858
--- /dev/null
+++ b/packages/core/src/lib/ui-header/index.ts
@@ -0,0 +1 @@
+export * from './ui-header'
diff --git a/packages/core/src/lib/ui-header/ui-header.module.css b/packages/core/src/lib/ui-header/ui-header.module.css
new file mode 100644
index 0000000..f5a471f
--- /dev/null
+++ b/packages/core/src/lib/ui-header/ui-header.module.css
@@ -0,0 +1,28 @@
+.header {
+ height: rem(56px);
+ background-color: var(--mantine-color-body);
+ padding-left: var(--mantine-spacing-md);
+ padding-right: var(--mantine-spacing-md);
+}
+
+.inner {
+ height: rem(56px);
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.link {
+ display: block;
+ line-height: 1;
+ padding: rem(8px) rem(12px);
+ border-radius: var(--mantine-radius-sm);
+ text-decoration: none;
+ color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-0));
+ font-size: var(--mantine-font-size-sm);
+ font-weight: 500;
+
+ @mixin hover {
+ background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
+ }
+}
diff --git a/packages/core/src/lib/ui-header/ui-header.tsx b/packages/core/src/lib/ui-header/ui-header.tsx
new file mode 100644
index 0000000..05e6ab5
--- /dev/null
+++ b/packages/core/src/lib/ui-header/ui-header.tsx
@@ -0,0 +1,51 @@
+import { Anchor, Burger, Group } from '@mantine/core'
+import { ReactNode } from 'react'
+import { Link } from 'react-router-dom'
+import { UiLogo, UiLogoType } from '../ui-logo'
+
+import classes from './ui-header.module.css'
+
+export interface UiHeaderProps {
+ base?: string
+ logo?: ReactNode
+ logoSmall?: ReactNode
+ links?: UiHeaderLink[]
+ opened?: boolean
+ profile?: ReactNode
+ toggle?: () => void
+}
+export interface UiHeaderLink {
+ link: string
+ label: string
+}
+
+export function UiHeader({ base, links = [], logo, logoSmall, opened, profile, toggle }: UiHeaderProps) {
+ const items = links.map((link) => (
+
+ {link.label}
+
+ ))
+
+ const burger = toggle ? : null
+
+ return (
+
+
+
+
+ {burger}
+
+ {logoSmall ?? }
+ {logo ?? }
+
+
+
+ {items}
+
+
+
+
{profile}
+
+
+ )
+}
diff --git a/packages/core/src/lib/ui-layout/index.ts b/packages/core/src/lib/ui-layout/index.ts
new file mode 100644
index 0000000..2a362b5
--- /dev/null
+++ b/packages/core/src/lib/ui-layout/index.ts
@@ -0,0 +1 @@
+export * from './ui-layout'
diff --git a/packages/core/src/lib/ui-layout/ui-layout.tsx b/packages/core/src/lib/ui-layout/ui-layout.tsx
new file mode 100644
index 0000000..88ce7d9
--- /dev/null
+++ b/packages/core/src/lib/ui-layout/ui-layout.tsx
@@ -0,0 +1,19 @@
+import { AppShell, AppShellProps, Loader, rem } from '@mantine/core'
+import { ReactNode, Suspense } from 'react'
+
+export function UiLayout({
+ children,
+ header,
+ headerHeight = rem(56),
+ ...props
+}: Omit & { children: ReactNode; header: ReactNode; headerHeight?: string }) {
+ return (
+
+ {header}
+
+
+ }>{children}
+
+
+ )
+}
diff --git a/packages/core/src/lib/ui-menu/index.ts b/packages/core/src/lib/ui-menu/index.ts
new file mode 100644
index 0000000..19b7d72
--- /dev/null
+++ b/packages/core/src/lib/ui-menu/index.ts
@@ -0,0 +1 @@
+export * from './ui-menu'
diff --git a/packages/core/src/lib/ui-menu/ui-menu.tsx b/packages/core/src/lib/ui-menu/ui-menu.tsx
new file mode 100644
index 0000000..08048f3
--- /dev/null
+++ b/packages/core/src/lib/ui-menu/ui-menu.tsx
@@ -0,0 +1,41 @@
+import { ActionIcon, Menu, MenuItemProps, MenuProps } from '@mantine/core'
+import { ReactNode } from 'react'
+
+export interface UiMenuItem extends MenuItemProps {
+ label: string
+ type: 'label' | 'divider' | 'item'
+ onClick?: () => void
+}
+
+export interface UiMenuProps extends MenuProps {
+ items: UiMenuItem[]
+ icon: ReactNode
+}
+
+export function UiMenu({ items, icon, ...props }: UiMenuProps) {
+ return (
+
+ )
+}
diff --git a/packages/core/src/lib/ui-theme-switch/index.ts b/packages/core/src/lib/ui-theme-switch/index.ts
new file mode 100644
index 0000000..3e662b6
--- /dev/null
+++ b/packages/core/src/lib/ui-theme-switch/index.ts
@@ -0,0 +1 @@
+export * from './ui-theme-switch'
diff --git a/packages/core/src/lib/ui-theme-switch/ui-theme-switch.tsx b/packages/core/src/lib/ui-theme-switch/ui-theme-switch.tsx
new file mode 100644
index 0000000..253180b
--- /dev/null
+++ b/packages/core/src/lib/ui-theme-switch/ui-theme-switch.tsx
@@ -0,0 +1,26 @@
+import { rem, Switch, SwitchProps, useMantineTheme } from '@mantine/core'
+import { IconMoonStars, IconSun } from '@tabler/icons-react'
+import { useUiColorScheme } from '../ui-theme'
+
+export function UiThemeSwitch(props: SwitchProps) {
+ const theme = useMantineTheme()
+
+ const sunIcon =
+
+ const moonIcon = (
+
+ )
+ const { toggleColorScheme, colorScheme } = useUiColorScheme()
+
+ return (
+ toggleColorScheme()}
+ checked={colorScheme === 'dark'}
+ {...props}
+ />
+ )
+}
diff --git a/packages/core/src/lib/ui-theme/use-ui-breakpoints.ts b/packages/core/src/lib/ui-theme/use-ui-breakpoints.ts
new file mode 100644
index 0000000..e475240
--- /dev/null
+++ b/packages/core/src/lib/ui-theme/use-ui-breakpoints.ts
@@ -0,0 +1,13 @@
+import { useMantineTheme } from '@mantine/core'
+import { useMediaQuery } from '@mantine/hooks'
+
+export function useUiBreakpoints() {
+ const { breakpoints } = useMantineTheme()
+ const isSm = useMediaQuery(`(max-width: ${breakpoints.sm})`)
+ const isMd = useMediaQuery(`(max-width: ${breakpoints.md})`)
+
+ return {
+ isSm: isSm ?? false,
+ isMd: isMd ?? false,
+ }
+}
diff --git a/packages/generators/src/generators/component/__snapshots__/component-generator.spec.ts.snap b/packages/generators/src/generators/component/__snapshots__/component-generator.spec.ts.snap
index da1fe8a..29ac563 100644
--- a/packages/generators/src/generators/component/__snapshots__/component-generator.spec.ts.snap
+++ b/packages/generators/src/generators/component/__snapshots__/component-generator.spec.ts.snap
@@ -182,7 +182,7 @@ exports[`component generator should create files for card 1`] = `
"export function UiCard({ loading, title, ...props }: UiCardProps) {",
"const { isSm } = useUiBreakpoints();",
"return (",
- "",
+ "",
"{title ? (",
"",
"{typeof title === 'string' ? (",
@@ -626,6 +626,170 @@ exports[`component generator should create files for group 1`] = `
}
`;
+exports[`component generator should create files for header 1`] = `
+{
+ ".prettierrc": {
+ "content": [
+ "{ "singleQuote": true }",
+ ],
+ "isBinary": false,
+ "path": "./.prettierrc",
+ },
+ "nx.json": {
+ "content": [
+ "{",
+ ""affected": { "defaultBase": "main" },",
+ ""targetDefaults": {",
+ ""build": { "cache": true },",
+ ""lint": { "cache": true },",
+ ""e2e": { "cache": true }",
+ "}",
+ "}",
+ ],
+ "isBinary": false,
+ "path": "./nx.json",
+ },
+ "package.json": {
+ "content": [
+ "{",
+ ""name": "@proj/source",",
+ ""dependencies": {},",
+ ""devDependencies": {}",
+ "}",
+ ],
+ "isBinary": false,
+ "path": "./package.json",
+ },
+ "test": {
+ "children": {
+ "index.ts": {
+ "content": [
+ "export * from './ui-header';",
+ ],
+ "isBinary": false,
+ "path": "./test/index.ts",
+ },
+ "ui-header.module.css": {
+ "content": [
+ ".header {",
+ "height: rem(56px);",
+ "background-color: var(--mantine-color-body);",
+ "padding-left: var(--mantine-spacing-md);",
+ "padding-right: var(--mantine-spacing-md);",
+ "}",
+ ".inner {",
+ "height: rem(56px);",
+ "display: flex;",
+ "justify-content: space-between;",
+ "align-items: center;",
+ "}",
+ ".link {",
+ "display: block;",
+ "line-height: 1;",
+ "padding: rem(8px) rem(12px);",
+ "border-radius: var(--mantine-radius-sm);",
+ "text-decoration: none;",
+ "color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-0));",
+ "font-size: var(--mantine-font-size-sm);",
+ "font-weight: 500;",
+ "@mixin hover {",
+ "background-color: light-dark(",
+ "var(--mantine-color-gray-0),",
+ "var(--mantine-color-dark-6)",
+ ");",
+ "}",
+ "}",
+ ],
+ "isBinary": false,
+ "path": "./test/ui-header.module.css",
+ },
+ "ui-header.tsx": {
+ "content": [
+ "import { Anchor, Burger, Group } from '@mantine/core';",
+ "import { ReactNode } from 'react';",
+ "import { Link } from 'react-router-dom';",
+ "import { UiLogo, UiLogoType } from '../ui-logo';",
+ "import classes from './ui-header.module.css';",
+ "export interface UiHeaderProps {",
+ "base?: string;",
+ "logo?: ReactNode;",
+ "logoSmall?: ReactNode;",
+ "links?: UiHeaderLink[];",
+ "opened?: boolean;",
+ "profile?: ReactNode;",
+ "toggle?: () => void;",
+ "}",
+ "export interface UiHeaderLink {",
+ "link: string;",
+ "label: string;",
+ "}",
+ "export function UiHeader({",
+ "base,",
+ "links = [],",
+ "logo,",
+ "logoSmall,",
+ "opened,",
+ "profile,",
+ "toggle,",
+ "}: UiHeaderProps) {",
+ "const items = links.map((link) => (",
+ "",
+ "{link.label}",
+ "",
+ "));",
+ "const burger = toggle ? (",
+ "",
+ ") : null;",
+ "return (",
+ "",
+ "",
+ "
",
+ "",
+ "{burger}",
+ "",
+ "",
+ "{logoSmall ?? }",
+ "",
+ "",
+ "{logo ?? }",
+ "",
+ "",
+ "",
+ "",
+ "{items}",
+ "",
+ "",
+ "
{profile}",
+ "
",
+ "",
+ ");",
+ "}",
+ ],
+ "isBinary": false,
+ "path": "./test/ui-header.tsx",
+ },
+ },
+ "path": "./test",
+ },
+ "tsconfig.base.json": {
+ "content": [
+ "{",
+ ""compilerOptions": {",
+ ""paths": {}",
+ "}",
+ "}",
+ ],
+ "isBinary": false,
+ "path": "./tsconfig.base.json",
+ },
+}
+`;
+
exports[`component generator should create files for logo 1`] = `
{
".prettierrc": {
@@ -850,6 +1014,110 @@ exports[`component generator should create files for logo 1`] = `
}
`;
+exports[`component generator should create files for menu 1`] = `
+{
+ ".prettierrc": {
+ "content": [
+ "{ "singleQuote": true }",
+ ],
+ "isBinary": false,
+ "path": "./.prettierrc",
+ },
+ "nx.json": {
+ "content": [
+ "{",
+ ""affected": { "defaultBase": "main" },",
+ ""targetDefaults": {",
+ ""build": { "cache": true },",
+ ""lint": { "cache": true },",
+ ""e2e": { "cache": true }",
+ "}",
+ "}",
+ ],
+ "isBinary": false,
+ "path": "./nx.json",
+ },
+ "package.json": {
+ "content": [
+ "{",
+ ""name": "@proj/source",",
+ ""dependencies": {},",
+ ""devDependencies": {}",
+ "}",
+ ],
+ "isBinary": false,
+ "path": "./package.json",
+ },
+ "test": {
+ "children": {
+ "index.ts": {
+ "content": [
+ "export * from './ui-menu';",
+ ],
+ "isBinary": false,
+ "path": "./test/index.ts",
+ },
+ "ui-menu.tsx": {
+ "content": [
+ "import { ActionIcon, Menu, MenuItemProps, MenuProps } from '@mantine/core';",
+ "import { ReactNode } from 'react';",
+ "export interface UiMenuItem extends MenuItemProps {",
+ "label: string;",
+ "type: 'label' | 'divider' | 'item';",
+ "onClick?: () => void;",
+ "}",
+ "export interface UiMenuProps extends MenuProps {",
+ "items: UiMenuItem[];",
+ "icon: ReactNode;",
+ "}",
+ "export function UiMenu({ items, icon, ...props }: UiMenuProps) {",
+ "return (",
+ "",
+ ");",
+ "}",
+ ],
+ "isBinary": false,
+ "path": "./test/ui-menu.tsx",
+ },
+ },
+ "path": "./test",
+ },
+ "tsconfig.base.json": {
+ "content": [
+ "{",
+ ""compilerOptions": {",
+ ""paths": {}",
+ "}",
+ "}",
+ ],
+ "isBinary": false,
+ "path": "./tsconfig.base.json",
+ },
+}
+`;
+
exports[`component generator should create files for search-input 1`] = `
{
".prettierrc": {
diff --git a/packages/generators/src/generators/component/component-generator-schema.d.ts b/packages/generators/src/generators/component/component-generator-schema.d.ts
index 5c182ec..424ff14 100644
--- a/packages/generators/src/generators/component/component-generator-schema.d.ts
+++ b/packages/generators/src/generators/component/component-generator-schema.d.ts
@@ -20,11 +20,15 @@ export interface ComponentGeneratorSchema {
| 'copy'
| 'debug'
| 'group'
+ | 'header'
+ | 'layout'
| 'logo'
+ | 'menu'
| 'search-input'
| 'stack'
| 'tab-routes'
| 'theme'
+ | 'theme-switch'
| 'time'
| 'toast'
/**
diff --git a/packages/generators/src/generators/component/component-generator-schema.json b/packages/generators/src/generators/component/component-generator-schema.json
index 133880b..072b6b1 100644
--- a/packages/generators/src/generators/component/component-generator-schema.json
+++ b/packages/generators/src/generators/component/component-generator-schema.json
@@ -22,11 +22,15 @@
"copy",
"debug",
"group",
+ "header",
+ "layout",
"logo",
+ "menu",
"search-input",
"stack",
"tab-routes",
"theme",
+ "theme-switch",
"time",
"toast"
],
diff --git a/packages/generators/src/generators/component/files/card/__prefixFileName__-card.tsx.template b/packages/generators/src/generators/component/files/card/__prefixFileName__-card.tsx.template
index ddc8e71..cecd4b6 100644
--- a/packages/generators/src/generators/component/files/card/__prefixFileName__-card.tsx.template
+++ b/packages/generators/src/generators/component/files/card/__prefixFileName__-card.tsx.template
@@ -13,7 +13,7 @@ export function <%= prefix.className %>Card({ loading, title, ...props }: <%= pr
const { isSm } = use<%= prefix.className %>Breakpoints()
return (
-
+
{title ? (
{typeof title === 'string' ? <<%= prefix.className %>CardTitle>{title}<%= prefix.className %>CardTitle> : title}
) : null}
diff --git a/packages/generators/src/generators/component/files/header/__prefixFileName__-header.module.css.template b/packages/generators/src/generators/component/files/header/__prefixFileName__-header.module.css.template
new file mode 100644
index 0000000..f5a471f
--- /dev/null
+++ b/packages/generators/src/generators/component/files/header/__prefixFileName__-header.module.css.template
@@ -0,0 +1,28 @@
+.header {
+ height: rem(56px);
+ background-color: var(--mantine-color-body);
+ padding-left: var(--mantine-spacing-md);
+ padding-right: var(--mantine-spacing-md);
+}
+
+.inner {
+ height: rem(56px);
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.link {
+ display: block;
+ line-height: 1;
+ padding: rem(8px) rem(12px);
+ border-radius: var(--mantine-radius-sm);
+ text-decoration: none;
+ color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-0));
+ font-size: var(--mantine-font-size-sm);
+ font-weight: 500;
+
+ @mixin hover {
+ background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
+ }
+}
diff --git a/packages/generators/src/generators/component/files/header/__prefixFileName__-header.tsx.template b/packages/generators/src/generators/component/files/header/__prefixFileName__-header.tsx.template
new file mode 100644
index 0000000..daa44bf
--- /dev/null
+++ b/packages/generators/src/generators/component/files/header/__prefixFileName__-header.tsx.template
@@ -0,0 +1,51 @@
+import { Anchor, Burger, Group } from '@mantine/core'
+import { ReactNode } from 'react'
+import { Link } from 'react-router-dom'
+import { <%= prefix.className %>Logo, <%= prefix.className %>LogoType } from '../<%= prefix.fileName %>-logo'
+
+import classes from './<%= prefix.fileName %>-header.module.css'
+
+export interface <%= prefix.className %>HeaderProps {
+ base?: string
+ logo?: ReactNode
+ logoSmall?: ReactNode
+ links?: <%= prefix.className %>HeaderLink[]
+ opened?: boolean
+ profile?: ReactNode
+ toggle?: () => void
+}
+export interface <%= prefix.className %>HeaderLink {
+ link: string
+ label: string
+}
+
+export function <%= prefix.className %>Header({ base, links = [], logo, logoSmall, opened, profile, toggle }: <%= prefix.className %>HeaderProps) {
+ const items = links.map((link) => (
+
+ {link.label}
+
+ ))
+
+ const burger = toggle ? : null
+
+ return (
+
+
+
+
+ {burger}
+
+ {logoSmall ?? <<%= prefix.className %>Logo height={28} />}
+ {logo ?? <<%= prefix.className %>LogoType height={28} />}
+
+
+
+ {items}
+
+
+
+
{profile}
+
+
+ )
+}
diff --git a/packages/generators/src/generators/component/files/header/index.ts.template b/packages/generators/src/generators/component/files/header/index.ts.template
new file mode 100644
index 0000000..e08e652
--- /dev/null
+++ b/packages/generators/src/generators/component/files/header/index.ts.template
@@ -0,0 +1 @@
+export * from './<%= prefixFileName %>-header'
diff --git a/packages/generators/src/generators/component/files/layout/__prefixFileName__-layout.tsx.template b/packages/generators/src/generators/component/files/layout/__prefixFileName__-layout.tsx.template
new file mode 100644
index 0000000..c6469de
--- /dev/null
+++ b/packages/generators/src/generators/component/files/layout/__prefixFileName__-layout.tsx.template
@@ -0,0 +1,19 @@
+import { AppShell, AppShellProps, Loader, rem } from '@mantine/core'
+import { ReactNode, Suspense } from 'react'
+
+export function <%= prefix.className %>Layout({
+ children,
+ header,
+ headerHeight = rem(56),
+ ...props
+}: Omit & { children: ReactNode; header: ReactNode; headerHeight?: string }) {
+ return (
+
+ {header}
+
+
+ }>{children}
+
+
+ )
+}
diff --git a/packages/generators/src/generators/component/files/layout/index.ts.template b/packages/generators/src/generators/component/files/layout/index.ts.template
new file mode 100644
index 0000000..29093d6
--- /dev/null
+++ b/packages/generators/src/generators/component/files/layout/index.ts.template
@@ -0,0 +1 @@
+export * from './<%= prefixFileName %>-layout'
diff --git a/packages/generators/src/generators/component/files/menu/__prefixFileName__-menu.tsx.template b/packages/generators/src/generators/component/files/menu/__prefixFileName__-menu.tsx.template
new file mode 100644
index 0000000..cae7aae
--- /dev/null
+++ b/packages/generators/src/generators/component/files/menu/__prefixFileName__-menu.tsx.template
@@ -0,0 +1,41 @@
+import { ActionIcon, Menu, MenuItemProps, MenuProps } from '@mantine/core'
+import { ReactNode } from 'react'
+
+export interface <%= prefix.className %>MenuItem extends MenuItemProps {
+ label: string
+ type: 'label' | 'divider' | 'item'
+ onClick?: () => void
+}
+
+export interface <%= prefix.className %>MenuProps extends MenuProps{
+ items: <%= prefix.className %>MenuItem[]
+ icon: ReactNode
+}
+
+export function <%= prefix.className %>Menu({ items, icon, ...props }: <%= prefix.className %>MenuProps) {
+ return (
+
+ )
+}
diff --git a/packages/generators/src/generators/component/files/menu/index.ts.template b/packages/generators/src/generators/component/files/menu/index.ts.template
new file mode 100644
index 0000000..fa90bda
--- /dev/null
+++ b/packages/generators/src/generators/component/files/menu/index.ts.template
@@ -0,0 +1 @@
+export * from './<%= prefixFileName %>-menu'
diff --git a/packages/generators/src/generators/component/files/theme-switch/__prefixFileName__-theme-switch.tsx.template b/packages/generators/src/generators/component/files/theme-switch/__prefixFileName__-theme-switch.tsx.template
new file mode 100644
index 0000000..9973e45
--- /dev/null
+++ b/packages/generators/src/generators/component/files/theme-switch/__prefixFileName__-theme-switch.tsx.template
@@ -0,0 +1,26 @@
+import { rem, Switch, SwitchProps, useMantineTheme } from '@mantine/core'
+import { IconMoonStars, IconSun } from '@tabler/icons-react'
+import { useUiColorScheme } from '../ui-theme'
+
+export function UiThemeSwitch(props: SwitchProps) {
+ const theme = useMantineTheme()
+
+ const sunIcon =
+
+ const moonIcon = (
+
+ )
+ const {toggleColorScheme, colorScheme} = useUiColorScheme()
+
+ return (
+ toggleColorScheme()}
+ checked={colorScheme === 'dark'}
+ {...props}
+ />
+ )
+}
diff --git a/packages/generators/src/generators/component/files/theme-switch/index.ts.template b/packages/generators/src/generators/component/files/theme-switch/index.ts.template
new file mode 100644
index 0000000..e230827
--- /dev/null
+++ b/packages/generators/src/generators/component/files/theme-switch/index.ts.template
@@ -0,0 +1 @@
+export * from './<%= prefixFileName %>-theme-switch'
diff --git a/packages/generators/src/generators/components/__snapshots__/components-generator.spec.ts.snap b/packages/generators/src/generators/components/__snapshots__/components-generator.spec.ts.snap
index e6412b4..25036e6 100644
--- a/packages/generators/src/generators/components/__snapshots__/components-generator.spec.ts.snap
+++ b/packages/generators/src/generators/components/__snapshots__/components-generator.spec.ts.snap
@@ -44,7 +44,9 @@ exports[`components generator should run successfully 1`] = `
"export * from './ui-copy';",
"export * from './ui-debug';",
"export * from './ui-group';",
+ "export * from './ui-header';",
"export * from './ui-logo';",
+ "export * from './ui-menu';",
"export * from './ui-search-input';",
"export * from './ui-stack';",
"export * from './ui-tab-routes';",
@@ -155,7 +157,7 @@ exports[`components generator should run successfully 1`] = `
"export function UiCard({ loading, title, ...props }: UiCardProps) {",
"const { isSm } = useUiBreakpoints();",
"return (",
- "",
+ "",
"{title ? (",
"",
"{typeof title === 'string' ? (",
@@ -393,6 +395,122 @@ exports[`components generator should run successfully 1`] = `
},
"path": "./test/ui-group",
},
+ "ui-header": {
+ "children": {
+ "index.ts": {
+ "content": [
+ "export * from './ui-header';",
+ ],
+ "isBinary": false,
+ "path": "./test/ui-header/index.ts",
+ },
+ "ui-header.module.css": {
+ "content": [
+ ".header {",
+ "height: rem(56px);",
+ "background-color: var(--mantine-color-body);",
+ "padding-left: var(--mantine-spacing-md);",
+ "padding-right: var(--mantine-spacing-md);",
+ "}",
+ ".inner {",
+ "height: rem(56px);",
+ "display: flex;",
+ "justify-content: space-between;",
+ "align-items: center;",
+ "}",
+ ".link {",
+ "display: block;",
+ "line-height: 1;",
+ "padding: rem(8px) rem(12px);",
+ "border-radius: var(--mantine-radius-sm);",
+ "text-decoration: none;",
+ "color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-0));",
+ "font-size: var(--mantine-font-size-sm);",
+ "font-weight: 500;",
+ "@mixin hover {",
+ "background-color: light-dark(",
+ "var(--mantine-color-gray-0),",
+ "var(--mantine-color-dark-6)",
+ ");",
+ "}",
+ "}",
+ ],
+ "isBinary": false,
+ "path": "./test/ui-header/ui-header.module.css",
+ },
+ "ui-header.tsx": {
+ "content": [
+ "import { Anchor, Burger, Group } from '@mantine/core';",
+ "import { ReactNode } from 'react';",
+ "import { Link } from 'react-router-dom';",
+ "import { UiLogo, UiLogoType } from '../ui-logo';",
+ "import classes from './ui-header.module.css';",
+ "export interface UiHeaderProps {",
+ "base?: string;",
+ "logo?: ReactNode;",
+ "logoSmall?: ReactNode;",
+ "links?: UiHeaderLink[];",
+ "opened?: boolean;",
+ "profile?: ReactNode;",
+ "toggle?: () => void;",
+ "}",
+ "export interface UiHeaderLink {",
+ "link: string;",
+ "label: string;",
+ "}",
+ "export function UiHeader({",
+ "base,",
+ "links = [],",
+ "logo,",
+ "logoSmall,",
+ "opened,",
+ "profile,",
+ "toggle,",
+ "}: UiHeaderProps) {",
+ "const items = links.map((link) => (",
+ "",
+ "{link.label}",
+ "",
+ "));",
+ "const burger = toggle ? (",
+ "",
+ ") : null;",
+ "return (",
+ "",
+ "",
+ "
",
+ "",
+ "{burger}",
+ "",
+ "",
+ "{logoSmall ?? }",
+ "",
+ "",
+ "{logo ?? }",
+ "",
+ "",
+ "",
+ "",
+ "{items}",
+ "",
+ "",
+ "
{profile}",
+ "
",
+ "",
+ ");",
+ "}",
+ ],
+ "isBinary": false,
+ "path": "./test/ui-header/ui-header.tsx",
+ },
+ },
+ "path": "./test/ui-header",
+ },
"ui-logo": {
"children": {
"index.ts": {
@@ -569,6 +687,62 @@ exports[`components generator should run successfully 1`] = `
},
"path": "./test/ui-logo",
},
+ "ui-menu": {
+ "children": {
+ "index.ts": {
+ "content": [
+ "export * from './ui-menu';",
+ ],
+ "isBinary": false,
+ "path": "./test/ui-menu/index.ts",
+ },
+ "ui-menu.tsx": {
+ "content": [
+ "import { ActionIcon, Menu, MenuItemProps, MenuProps } from '@mantine/core';",
+ "import { ReactNode } from 'react';",
+ "export interface UiMenuItem extends MenuItemProps {",
+ "label: string;",
+ "type: 'label' | 'divider' | 'item';",
+ "onClick?: () => void;",
+ "}",
+ "export interface UiMenuProps extends MenuProps {",
+ "items: UiMenuItem[];",
+ "icon: ReactNode;",
+ "}",
+ "export function UiMenu({ items, icon, ...props }: UiMenuProps) {",
+ "return (",
+ "",
+ ");",
+ "}",
+ ],
+ "isBinary": false,
+ "path": "./test/ui-menu/ui-menu.tsx",
+ },
+ },
+ "path": "./test/ui-menu",
+ },
"ui-search-input": {
"children": {
"index.ts": {
diff --git a/packages/generators/src/generators/components/components.ts b/packages/generators/src/generators/components/components.ts
index 8c2e077..5fd1021 100644
--- a/packages/generators/src/generators/components/components.ts
+++ b/packages/generators/src/generators/components/components.ts
@@ -7,11 +7,15 @@ export const components: ComponentGeneratorSchema['type'][] = [
'copy',
'debug',
'group',
+ 'header',
+ 'layout',
'logo',
+ 'menu',
'search-input',
'stack',
'tab-routes',
'theme',
+ 'theme-switch',
'time',
'toast',
]
diff --git a/packages/generators/src/generators/feature/files/demo/demo-feature-alerts.tsx.template b/packages/generators/src/generators/feature/files/demo/demo-feature-alerts.tsx.template
index 707bcb0..faee078 100644
--- a/packages/generators/src/generators/feature/files/demo/demo-feature-alerts.tsx.template
+++ b/packages/generators/src/generators/feature/files/demo/demo-feature-alerts.tsx.template
@@ -1,5 +1,5 @@
import { SimpleGrid } from '@mantine/core'
-import { <%= prefix.className %>Card, <%= prefix.className %>Alert, <%= prefix.className %>Error, <%= prefix.className %>Info, <%= prefix.className %>Success, <%= prefix.className %>Warning } from '<%= uiImport %>'
+import { <%= prefix.className %>Alert, <%= prefix.className %>Card, <%= prefix.className %>Error, <%= prefix.className %>Info, <%= prefix.className %>Success, <%= prefix.className %>Warning } from '<%= uiImport %>'
export function DemoFeatureAlerts() {
return (
diff --git a/packages/generators/src/generators/feature/files/demo/demo-feature-card.tsx.template b/packages/generators/src/generators/feature/files/demo/demo-feature-card.tsx.template
index 42f532e..0e139ac 100644
--- a/packages/generators/src/generators/feature/files/demo/demo-feature-card.tsx.template
+++ b/packages/generators/src/generators/feature/files/demo/demo-feature-card.tsx.template
@@ -3,6 +3,7 @@ import { <%= prefix.className %>Card, <%= prefix.className %>Stack } from '<%= u
export function DemoFeatureCard() {
return (
<<%= prefix.className %>Stack>
+ <<%= prefix.className %>Card title="Default Card">CARD CONTENT<%= prefix.className %>Card>
<<%= prefix.className %>Card withBorder title="Card" shadow="lg">
CARD CONTENT
<%= prefix.className %>Card>
diff --git a/packages/generators/src/generators/feature/files/demo/demo-feature-header.tsx.template b/packages/generators/src/generators/feature/files/demo/demo-feature-header.tsx.template
new file mode 100644
index 0000000..a88abb3
--- /dev/null
+++ b/packages/generators/src/generators/feature/files/demo/demo-feature-header.tsx.template
@@ -0,0 +1,136 @@
+import { ActionIcon, Avatar, Button, Group, rem, Text } from '@mantine/core'
+import { useDisclosure } from '@mantine/hooks'
+import { <%= prefix.className %>Card, <%= prefix.className %>Header, <%= prefix.className %>Menu, <%= prefix.className %>SearchInput, <%= prefix.className %>Stack } from '<%= uiImport %>'
+import {
+ IconDoorExit,
+ IconLetterP,
+ IconMessageCircle,
+ IconPhoto,
+ IconSearch,
+ IconSettings,
+ IconUser,
+ IconUserCog,
+} from '@tabler/icons-react'
+import { useState } from 'react'
+import { Route, Routes } from 'react-router-dom'
+
+export function DemoFeatureHeader() {
+ const [opened, { toggle }] = useDisclosure(false)
+ const [signedIn, setSignedIn] = useState(false)
+ return (
+ <<%= prefix.className %>Card title="Header">
+ <<%= prefix.className %>Stack gap="xl">
+ <<%= prefix.className %>Header
+ base="/demo/header"
+ opened={opened}
+ toggle={toggle}
+ links={[
+ { link: '/demo/header/about', label: 'Features' },
+ { link: '/demo/header/pricing', label: 'Pricing' },
+ { link: '/demo/header/learn', label: 'Learn' },
+ ]}
+ profile={<<%= prefix.className %>SearchInput text={{ size: 'sm' }} />}
+ />
+ <<%= prefix.className %>Header
+ profile={
+
+
+
+ }
+ />
+ <<%= prefix.className %>Header
+ base="/demo/header"
+ logo={
+
+
+
+ PubKey
+
+
+ }
+ logoSmall={}
+ />
+ <<%= prefix.className %>Header
+ profile={
+
+ {signedIn ? (
+ <<%= prefix.className %>Menu
+ position="bottom-end"
+ withArrow
+ arrowOffset={14}
+ icon={
+
+
+
+ }
+ items={[
+ { label: 'Application', type: 'label' },
+ {
+ label: 'Settings',
+ type: 'item',
+ leftSection: ,
+ },
+ {
+ label: 'Messages',
+ type: 'item',
+ leftSection: ,
+ },
+ {
+ label: 'Gallery',
+ type: 'item',
+ leftSection: ,
+ },
+ {
+ label: 'Search',
+ type: 'item',
+ leftSection: ,
+ rightSection: (
+
+ ⌘K
+
+ ),
+ },
+ {
+ label: 'Account',
+ type: 'label',
+ },
+ {
+ label: 'Profile',
+ type: 'item',
+ leftSection: ,
+ },
+ {
+ label: 'Sign out',
+ type: 'item',
+ leftSection: ,
+ onClick: () => setSignedIn(false),
+ },
+ ]}
+ />
+ ) : (
+
+ )}
+
+ }
+ />
+ <<%= prefix.className %>Stack hiddenFrom="md">
+ {opened ? (
+
+ ) : (
+
+ )}
+ <%= prefix.className %>Stack>
+
+
+ About} />
+ Pricing} />
+ Learn} />
+
+ <%= prefix.className %>Stack>
+ <%= prefix.className %>Card>
+ )
+}
diff --git a/packages/generators/src/generators/feature/files/demo/demo-feature-logo.tsx.template b/packages/generators/src/generators/feature/files/demo/demo-feature-logo.tsx.template
index 97662f6..f3b442d 100644
--- a/packages/generators/src/generators/feature/files/demo/demo-feature-logo.tsx.template
+++ b/packages/generators/src/generators/feature/files/demo/demo-feature-logo.tsx.template
@@ -1,13 +1,40 @@
import { SimpleGrid } from '@mantine/core'
-import { <%= prefix.className %>Card, <%= prefix.className %>LogoType, <%= prefix.className %>LogoTypeBlack, <%= prefix.className %>LogoTypeWhite } from '<%= uiImport %>'
+import {
+ <%= prefix.className %>Card,
+ <%= prefix.className %>Group,
+ <%= prefix.className %>Logo,
+ <%= prefix.className %>LogoType,
+ <%= prefix.className %>LogoTypeBlack,
+ <%= prefix.className %>LogoTypeWhite,
+ <%= prefix.className %>Stack,
+ <%= prefix.className %>ThemeSwitch,
+} from '<%= uiImport %>'
export function DemoFeatureLogo() {
return (
- <<%= prefix.className %>Card title="Logo">
+ <<%= prefix.className %>Card title="Logo and LogoType">
- <<%= prefix.className %>LogoType height={64} />
- <<%= prefix.className %>LogoTypeBlack height={64} />
- <<%= prefix.className %>LogoTypeWhite height={64} />
+ <<%= prefix.className %>Stack align="center">
+ <<%= prefix.className %>Logo height={64} />
+ <%= prefix.className %>Stack>
+ <<%= prefix.className %>Stack align="center">
+ <<%= prefix.className %>Logo height={128} />
+ <%= prefix.className %>Stack>
+ <<%= prefix.className %>Stack align="center">
+ <<%= prefix.className %>LogoTypeBlack height={64} />
+ <%= prefix.className %>Stack>
+ <<%= prefix.className %>Stack align="center">
+ <<%= prefix.className %>LogoTypeWhite height={64} />
+ <%= prefix.className %>Stack>
+ <<%= prefix.className %>Stack align="center">
+ <<%= prefix.className %>LogoType height={64} />
+ <%= prefix.className %>Stack>
+ <<%= prefix.className %>Group>
+ <<%= prefix.className %>Stack align="center">
+ Toggle the theme to see the logo change color.
+ <<%= prefix.className %>ThemeSwitch />
+ <%= prefix.className %>Stack>
+ <%= prefix.className %>Group>
<%= prefix.className %>Card>
)
diff --git a/packages/generators/src/generators/feature/files/demo/demo-feature-menu.tsx.template b/packages/generators/src/generators/feature/files/demo/demo-feature-menu.tsx.template
new file mode 100644
index 0000000..7b430e8
--- /dev/null
+++ b/packages/generators/src/generators/feature/files/demo/demo-feature-menu.tsx.template
@@ -0,0 +1,84 @@
+import { Avatar, rem, Text } from '@mantine/core'
+import { <%= prefix.className %>Card, <%= prefix.className %>Group, <%= prefix.className %>Menu, <%= prefix.className %>MenuItem, use<%= prefix.className %>ColorScheme } from '<%= uiImport %>'
+import {
+ IconDoorExit,
+ IconMessageCircle,
+ IconMoon,
+ IconPhoto,
+ IconSearch,
+ IconSettings,
+ IconSun,
+ IconUser,
+ IconUserCog,
+} from '@tabler/icons-react'
+import { ReactNode } from 'react'
+
+export function DemoFeatureMenu() {
+ const { colorScheme, toggleColorScheme } = use<%= prefix.className %>ColorScheme()
+ const icon: ReactNode = (
+
+
+
+ )
+ const items: <%= prefix.className %>MenuItem[] = [
+ { label: 'Application', type: 'label' },
+ {
+ label: 'Settings',
+ type: 'item',
+ leftSection: ,
+ },
+ {
+ label: 'Messages',
+ type: 'item',
+ leftSection: ,
+ },
+ {
+ label: 'Gallery',
+ type: 'item',
+ leftSection: ,
+ },
+ {
+ label: 'Search',
+ type: 'item',
+ leftSection: ,
+ rightSection: (
+
+ ⌘K
+
+ ),
+ },
+ {
+ label: `${colorScheme === 'dark' ? 'Light' : 'Dark'} theme`,
+ type: 'item',
+ leftSection:
+ colorScheme === 'dark' ? (
+
+ ) : (
+
+ ),
+ onClick: () => toggleColorScheme(),
+ },
+ {
+ label: 'Account',
+ type: 'label',
+ },
+ {
+ label: 'Profile',
+ type: 'item',
+ leftSection: ,
+ },
+ {
+ label: 'Sign out',
+ type: 'item',
+ leftSection: ,
+ },
+ ]
+ return (
+ <<%= prefix.className %>Card title="Menu">
+ <<%= prefix.className %>Group justify="space-between">
+ <<%= prefix.className %>Menu icon={icon} items={items} />
+ <<%= prefix.className %>Menu icon={icon} items={items} position="bottom-end" />
+ <%= prefix.className %>Group>
+ <%= prefix.className %>Card>
+ )
+}
diff --git a/packages/generators/src/generators/feature/files/demo/demo-feature-search-input.tsx.template b/packages/generators/src/generators/feature/files/demo/demo-feature-search-input.tsx.template
index 90edaa6..0e76158 100644
--- a/packages/generators/src/generators/feature/files/demo/demo-feature-search-input.tsx.template
+++ b/packages/generators/src/generators/feature/files/demo/demo-feature-search-input.tsx.template
@@ -1,22 +1,24 @@
import { SimpleGrid } from '@mantine/core'
-import { <%= prefix.className %>Card, <%= prefix.className %>Group, <%= prefix.className %>SearchInput } from '<%= uiImport %>'
+import { <%= prefix.className %>Card, <%= prefix.className %>Group, <%= prefix.className %>SearchInput, <%= prefix.className %>Stack } from '<%= uiImport %>'
export function DemoFeatureSearchInput() {
return (
<<%= prefix.className %>Card title="SearchInput">
-
- <<%= prefix.className %>SearchInput />
- <<%= prefix.className %>SearchInput icon={{ radius: 0 }} text={{ radius: 0 }} />
-
-
- <<%= prefix.className %>SearchInput />
+ <<%= prefix.className %>Stack>
+
+ <<%= prefix.className %>SearchInput />
+ <<%= prefix.className %>SearchInput icon={{ radius: 0 }} text={{ radius: 0 }} />
+
- <<%= prefix.className %>Group>
- <<%= prefix.className %>SearchInput />
<<%= prefix.className %>SearchInput />
- <<%= prefix.className %>SearchInput />
- <<%= prefix.className %>SearchInput />
- <%= prefix.className %>Group>
+
+ <<%= prefix.className %>Group>
+ <<%= prefix.className %>SearchInput />
+ <<%= prefix.className %>SearchInput />
+ <<%= prefix.className %>SearchInput />
+ <<%= prefix.className %>SearchInput />
+ <%= prefix.className %>Group>
+ <%= prefix.className %>Stack>
<%= prefix.className %>Card>
)
}
diff --git a/packages/generators/src/generators/feature/files/demo/demo-feature-tab-routes.tsx.template b/packages/generators/src/generators/feature/files/demo/demo-feature-tab-routes.tsx.template
index c73d6ff..869539e 100644
--- a/packages/generators/src/generators/feature/files/demo/demo-feature-tab-routes.tsx.template
+++ b/packages/generators/src/generators/feature/files/demo/demo-feature-tab-routes.tsx.template
@@ -3,9 +3,9 @@ import { <%= prefix.className %>Card, <%= prefix.className %>TabRoutes } from '<
export function DemoFeatureTabRoutes() {
return (
- <<%= prefix.className %>Card title="Time">
+ <<%= prefix.className %>Card title="Tab Routes">
<<%= prefix.className %>TabRoutes
- baseUrl="/demo"
+ baseUrl="/demo/tab-routes"
tabs={[
{
value: 'overview',
diff --git a/packages/generators/src/generators/feature/files/demo/demo-feature.tsx.template b/packages/generators/src/generators/feature/files/demo/demo-feature.tsx.template
index 4e5f9d0..fda97c6 100644
--- a/packages/generators/src/generators/feature/files/demo/demo-feature.tsx.template
+++ b/packages/generators/src/generators/feature/files/demo/demo-feature.tsx.template
@@ -1,10 +1,15 @@
+import { Grid, NavLink } from '@mantine/core'
import { <%= prefix.className %>Container, <%= prefix.className %>Stack } from '<%= uiImport %>'
+import { ReactNode } from 'react'
+import { Link, Navigate, useLocation, useRoutes } from 'react-router-dom'
import { DemoFeatureAlerts } from './demo-feature-alerts'
import { DemoFeatureCard } from './demo-feature-card'
import { DemoFeatureCopy } from './demo-feature-copy'
import { DemoFeatureDebug } from './demo-feature-debug'
import { DemoFeatureGroup } from './demo-feature-group'
+import { DemoFeatureHeader } from './demo-feature-header'
import { DemoFeatureLogo } from './demo-feature-logo'
+import { DemoFeatureMenu } from './demo-feature-menu'
import { DemoFeatureSearchInput } from './demo-feature-search-input'
import { DemoFeatureStack } from './demo-feature-stack'
import { DemoFeatureTabRoutes } from './demo-feature-tab-routes'
@@ -12,21 +17,47 @@ import { DemoFeatureTime } from './demo-feature-time'
import { DemoFeatureToast } from './demo-feature-toast'
export function DemoFeature() {
- return (
- <<%= prefix.className %>Container>
- <<%= prefix.className %>Stack>
-
-
-
-
-
-
-
-
-
-
-
- <%= prefix.className %>Stack>
- <%= prefix.className %>Container>
- )
+const { pathname } = useLocation()
+const demos: {
+path: string
+label: string
+element: ReactNode
+}[] = [
+{ path: 'alerts', label: 'Alerts', element: },
+{ path: 'card', label: 'Card', element: },
+{ path: 'copy', label: 'Copy', element: },
+{ path: 'debug', label: 'Debug', element: },
+{ path: 'group', label: 'Group', element: },
+{ path: 'header', label: 'Header', element: },
+{ path: 'logo', label: 'Logo', element: },
+{ path: 'menu', label: 'Menu', element: },
+{ path: 'search-input', label: 'Search Input', element: },
+{ path: 'stack', label: 'Stack', element: },
+{ path: 'tab-routes', label: 'Tab Routes', element: },
+{ path: 'time', label: 'Time', element: },
+{ path: 'toast', label: 'Toast', element: },
+]
+
+const routes = useRoutes([
+{ index: true, element: },
+...demos.map((demo) => ({ path: `${demo.path}/*`, element: demo.element })),
+])
+
+return (
+<<%= prefix.className %>Container>
+
+
+ {demos.map((demo) => {
+ const to = `/demo/${demo.path}`
+ return (
+
+ )
+ })}
+
+
+ <<%= prefix.className %>Stack gap="xl">{routes}<%= prefix.className %>Stack>
+
+
+<%= prefix.className %>Container>
+)
}
diff --git a/packages/generators/src/generators/theme/__snapshots__/theme-generator.spec.ts.snap b/packages/generators/src/generators/theme/__snapshots__/theme-generator.spec.ts.snap
index 08e6d7b..4c4eed7 100644
--- a/packages/generators/src/generators/theme/__snapshots__/theme-generator.spec.ts.snap
+++ b/packages/generators/src/generators/theme/__snapshots__/theme-generator.spec.ts.snap
@@ -204,7 +204,9 @@ exports[`theme generator should run successfully 1`] = `
"export * from './ui-copy';",
"export * from './ui-debug';",
"export * from './ui-group';",
+ "export * from './ui-header';",
"export * from './ui-logo';",
+ "export * from './ui-menu';",
"export * from './ui-search-input';",
"export * from './ui-stack';",
"export * from './ui-tab-routes';",
@@ -315,7 +317,7 @@ exports[`theme generator should run successfully 1`] = `
"export function UiCard({ loading, title, ...props }: UiCardProps) {",
"const { isSm } = useUiBreakpoints();",
"return (",
- "",
+ "",
"{title ? (",
"",
"{typeof title === 'string' ? (",
@@ -553,6 +555,122 @@ exports[`theme generator should run successfully 1`] = `
},
"path": "./test-target/src/app/ui/ui-group",
},
+ "ui-header": {
+ "children": {
+ "index.ts": {
+ "content": [
+ "export * from './ui-header';",
+ ],
+ "isBinary": false,
+ "path": "./test-target/src/app/ui/ui-header/index.ts",
+ },
+ "ui-header.module.css": {
+ "content": [
+ ".header {",
+ "height: rem(56px);",
+ "background-color: var(--mantine-color-body);",
+ "padding-left: var(--mantine-spacing-md);",
+ "padding-right: var(--mantine-spacing-md);",
+ "}",
+ ".inner {",
+ "height: rem(56px);",
+ "display: flex;",
+ "justify-content: space-between;",
+ "align-items: center;",
+ "}",
+ ".link {",
+ "display: block;",
+ "line-height: 1;",
+ "padding: rem(8px) rem(12px);",
+ "border-radius: var(--mantine-radius-sm);",
+ "text-decoration: none;",
+ "color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-0));",
+ "font-size: var(--mantine-font-size-sm);",
+ "font-weight: 500;",
+ "@mixin hover {",
+ "background-color: light-dark(",
+ "var(--mantine-color-gray-0),",
+ "var(--mantine-color-dark-6)",
+ ");",
+ "}",
+ "}",
+ ],
+ "isBinary": false,
+ "path": "./test-target/src/app/ui/ui-header/ui-header.module.css",
+ },
+ "ui-header.tsx": {
+ "content": [
+ "import { Anchor, Burger, Group } from '@mantine/core';",
+ "import { ReactNode } from 'react';",
+ "import { Link } from 'react-router-dom';",
+ "import { UiLogo, UiLogoType } from '../ui-logo';",
+ "import classes from './ui-header.module.css';",
+ "export interface UiHeaderProps {",
+ "base?: string;",
+ "logo?: ReactNode;",
+ "logoSmall?: ReactNode;",
+ "links?: UiHeaderLink[];",
+ "opened?: boolean;",
+ "profile?: ReactNode;",
+ "toggle?: () => void;",
+ "}",
+ "export interface UiHeaderLink {",
+ "link: string;",
+ "label: string;",
+ "}",
+ "export function UiHeader({",
+ "base,",
+ "links = [],",
+ "logo,",
+ "logoSmall,",
+ "opened,",
+ "profile,",
+ "toggle,",
+ "}: UiHeaderProps) {",
+ "const items = links.map((link) => (",
+ "",
+ "{link.label}",
+ "",
+ "));",
+ "const burger = toggle ? (",
+ "",
+ ") : null;",
+ "return (",
+ "",
+ "",
+ "
",
+ "",
+ "{burger}",
+ "",
+ "",
+ "{logoSmall ?? }",
+ "",
+ "",
+ "{logo ?? }",
+ "",
+ "",
+ "",
+ "",
+ "{items}",
+ "",
+ "",
+ "
{profile}",
+ "
",
+ "",
+ ");",
+ "}",
+ ],
+ "isBinary": false,
+ "path": "./test-target/src/app/ui/ui-header/ui-header.tsx",
+ },
+ },
+ "path": "./test-target/src/app/ui/ui-header",
+ },
"ui-logo": {
"children": {
"index.ts": {
@@ -729,6 +847,62 @@ exports[`theme generator should run successfully 1`] = `
},
"path": "./test-target/src/app/ui/ui-logo",
},
+ "ui-menu": {
+ "children": {
+ "index.ts": {
+ "content": [
+ "export * from './ui-menu';",
+ ],
+ "isBinary": false,
+ "path": "./test-target/src/app/ui/ui-menu/index.ts",
+ },
+ "ui-menu.tsx": {
+ "content": [
+ "import { ActionIcon, Menu, MenuItemProps, MenuProps } from '@mantine/core';",
+ "import { ReactNode } from 'react';",
+ "export interface UiMenuItem extends MenuItemProps {",
+ "label: string;",
+ "type: 'label' | 'divider' | 'item';",
+ "onClick?: () => void;",
+ "}",
+ "export interface UiMenuProps extends MenuProps {",
+ "items: UiMenuItem[];",
+ "icon: ReactNode;",
+ "}",
+ "export function UiMenu({ items, icon, ...props }: UiMenuProps) {",
+ "return (",
+ "",
+ ");",
+ "}",
+ ],
+ "isBinary": false,
+ "path": "./test-target/src/app/ui/ui-menu/ui-menu.tsx",
+ },
+ },
+ "path": "./test-target/src/app/ui/ui-menu",
+ },
"ui-search-input": {
"children": {
"index.ts": {
diff --git a/packages/generators/src/generators/theme/files/src/app/app-layout.tsx.template b/packages/generators/src/generators/theme/files/src/app/app-layout.tsx.template
index ad72442..2adeb96 100644
--- a/packages/generators/src/generators/theme/files/src/app/app-layout.tsx.template
+++ b/packages/generators/src/generators/theme/files/src/app/app-layout.tsx.template
@@ -1,43 +1,56 @@
-import { ActionIcon, Anchor, Box, Card, Group } from '@mantine/core'
-import { UiContainer, UiLogoType, useUiColorScheme, useUiTheme } from './ui'
-import { IconMoon, IconSun } from '@tabler/icons-react'
+import { ActionIcon, Avatar, Group, rem } from '@mantine/core'
+import { UiHeader, UiLayout, UiMenu, useUiColorScheme } from '@pubkey-ui/core'
+import { IconMoon, IconSettings, IconSun, IconUser, IconUserCog } from '@tabler/icons-react'
import { ReactNode } from 'react'
+import { AccountChecker } from './features/account/account-ui'
+import { ClusterChecker } from './features/cluster/cluster-ui'
export function AppLayout({ children }: { children: ReactNode }) {
- const { Link } = useUiTheme()
return (
-
-
-
-
-
-
-
-
-
- Dashboard
-
-
- Demo
-
-
-
-
-
-
+
+
+
+
+
+ }
+ items={[
+ { label: 'Account', type: 'label' },
+ { label: 'Settings', type: 'item', leftSection: , },
+ { label: 'Profile', type: 'item', leftSection: , },
+ ]}
+ />
+
+ }
+ />
+ }>
+
+
+
{children}
-
+
)
}
-function ThemeToggle() {
+export function ThemeToggle() {
const { toggleColorScheme, colorScheme } = useUiColorScheme()
return (
-
- toggleColorScheme()} variant="default" size="xl" aria-label="Toggle color scheme">
- {colorScheme === 'dark' ? : }
-
-
+ toggleColorScheme()} variant="default" size="xl" aria-label="Toggle color scheme">
+ {colorScheme === 'dark' ? : }
+
)
}