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 ( + + + + {icon} + + + + {items.map(({ label, type, ...item }) => { + switch (type) { + case 'label': + return {label} + case 'divider': + return + default: + return ( + + {label} + + ) + } + })} + + + ) +} 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 (", + "", + "", + "", + "{icon}", + "", + "", + "", + "{items.map(({ label, type, ...item }) => {", + "switch (type) {", + "case 'label':", + "return {label};", + "case 'divider':", + "return ;", + "default:", + "return (", + "", + "{label}", + "", + ");", + "}", + "})}", + "", + "", + ");", + "}", + ], + "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}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 ( + + + + {icon} + + + + {items.map(({ label, type, ...item }) => { + switch (type) { + case 'label': + return {label} + case 'divider': + return + default: + return ( + + {label} + + ) + } + })} + + + ) +} 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 (", + "", + "", + "", + "{icon}", + "", + "", + "", + "{items.map(({ label, type, ...item }) => {", + "switch (type) {", + "case 'label':", + "return {label};", + "case 'divider':", + "return ;", + "default:", + "return (", + "", + "{label}", + "", + ");", + "}", + "})}", + "", + "", + ");", + "}", + ], + "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 CONTENTCard> <<%= prefix.className %>Card withBorder title="Card" shadow="lg"> CARD CONTENT 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 ? ( + + ) : ( + + )} + Stack> + + + About} /> + Pricing} /> + Learn} /> + + Stack> + 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} /> + Stack> + <<%= prefix.className %>Stack align="center"> + <<%= prefix.className %>Logo height={128} /> + Stack> + <<%= prefix.className %>Stack align="center"> + <<%= prefix.className %>LogoTypeBlack height={64} /> + Stack> + <<%= prefix.className %>Stack align="center"> + <<%= prefix.className %>LogoTypeWhite height={64} /> + Stack> + <<%= prefix.className %>Stack align="center"> + <<%= prefix.className %>LogoType height={64} /> + Stack> + <<%= prefix.className %>Group> + <<%= prefix.className %>Stack align="center"> + Toggle the theme to see the logo change color. + <<%= prefix.className %>ThemeSwitch /> + Stack> + Group> 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" /> + Group> + 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 /> - Group> + + <<%= prefix.className %>Group> + <<%= prefix.className %>SearchInput /> + <<%= prefix.className %>SearchInput /> + <<%= prefix.className %>SearchInput /> + <<%= prefix.className %>SearchInput /> + Group> + Stack> 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> - - - - - - - - - - - - Stack> - 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}Stack> + + +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 (", + "", + "", + "", + "{icon}", + "", + "", + "", + "{items.map(({ label, type, ...item }) => {", + "switch (type) {", + "case 'label':", + "return {label};", + "case 'divider':", + "return ;", + "default:", + "return (", + "", + "{label}", + "", + ");", + "}", + "})}", + "", + "", + ");", + "}", + ], + "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' ? : } + ) }