@contember/cli-common 1.4.0-alpha.5 → 1.4.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contember/cli-common",
3
- "version": "1.4.0-alpha.5",
3
+ "version": "1.4.0-beta.2",
4
4
  "license": "Apache-2.0",
5
5
  "main": "dist/src/index.js",
6
6
  "typings": "dist/src/index.d.ts",
@@ -0,0 +1,8 @@
1
+ import { Slots } from '@contember/layout'
2
+ import { useClassName } from '@contember/react-utils'
3
+ import { forwardRef, memo } from 'react'
4
+
5
+ export const AppHeaderTitle = memo(forwardRef<HTMLHeadingElement, Slots.OwnTargetContainerProps>((props, forwardRef) => (
6
+ <h1 ref={forwardRef} {...props} className={useClassName('app-header-title', props.className)} />
7
+ )))
8
+ AppHeaderTitle.displayName = 'AppHeaderTitle'
@@ -0,0 +1 @@
1
+ export const LAYOUT_BREAKPOINT = 768
@@ -0,0 +1,18 @@
1
+ import { Directives } from '@contember/layout'
2
+ import { Intent } from '@contember/ui'
3
+ import { LAYOUT_BREAKPOINT } from './Constants'
4
+
5
+ export type DirectivesType = {
6
+ 'content-max-width': number | false | null | undefined
7
+ 'layout.theme-content': Intent | null | undefined
8
+ 'layout.theme-controls': Intent | null | undefined
9
+ }
10
+
11
+ export const initialDirectives: DirectivesType = Object.freeze({
12
+ 'content-max-width': LAYOUT_BREAKPOINT,
13
+ 'layout.theme-content': 'default',
14
+ 'layout.theme-controls': 'positive',
15
+ })
16
+
17
+ export const Directive = Directives.Directive as unknown as Directives.DirectiveComponentType<DirectivesType> // <DirectivesType>
18
+ export const useDirectives = Directives.useDirectives<DirectivesType>
@@ -1,12 +1,75 @@
1
- import * as React from 'react'
2
- import { ReactNode } from 'react'
3
- import { Layout as ContemberLayout } from '@contember/admin'
1
+ import { Button, Link, LogoutLink, PortalProvider, Scheme, Stack, VisuallyHidden } from '@contember/admin'
2
+ import { SafeAreaInsetsProvider } from '@contember/layout'
3
+ import { ColorSchemeProvider, useContainerWidth, useReferentiallyStableCallback, useSessionStorageState } from '@contember/react-utils'
4
+ import { colorSchemeClassName, contentThemeClassName, controlsThemeClassName } from '@contember/utilities'
5
+ import { CircleDashedIcon, LogOutIcon, MoonIcon, SunIcon } from 'lucide-react'
6
+ import { PropsWithChildren, memo } from 'react'
7
+ import { LAYOUT_BREAKPOINT } from './Constants'
8
+ import { useDirectives } from './Directives'
9
+ import { LayoutComponent } from './LayoutComponent'
10
+ import { SlotSources } from './Slots'
4
11
  import { Navigation } from './Navigation'
5
12
 
6
- export const Layout = (props: { children?: ReactNode }) => (
7
- <ContemberLayout
8
- sidebarHeader="{projectName}"
9
- navigation={<Navigation />}
10
- children={props.children}
11
- />
12
- )
13
+ export const Layout = memo(({ children }: PropsWithChildren) => {
14
+ const directives = useDirectives()
15
+ const width = useContainerWidth()
16
+
17
+ const [scheme, setScheme] = useSessionStorageState<Scheme>(
18
+ 'contember-admin-sandbox-scheme',
19
+ scheme => scheme ?? 'system',
20
+ )
21
+
22
+ const colorSchemeTheme = [
23
+ colorSchemeClassName(scheme),
24
+ contentThemeClassName(directives['layout.theme-content']),
25
+ controlsThemeClassName(directives['layout.theme-controls']),
26
+ ].join(' ')
27
+
28
+ return (
29
+ <SafeAreaInsetsProvider>
30
+ <ColorSchemeProvider scheme={scheme}>
31
+ <PortalProvider className={colorSchemeTheme}>
32
+ <LayoutComponent className={colorSchemeTheme}>
33
+ <SlotSources.Logo>
34
+ <Link to="index">
35
+ <Stack align="center" horizontal gap="small">
36
+ <VisuallyHidden className="whitespace-nowrap" hidden={width < LAYOUT_BREAKPOINT}>{'{projectName}'}</VisuallyHidden>
37
+ </Stack>
38
+ </Link>
39
+ </SlotSources.Logo>
40
+ <SlotSources.Navigation>
41
+ <Navigation />
42
+ </SlotSources.Navigation>
43
+
44
+ <SlotSources.Switchers>
45
+ <Button
46
+ square
47
+ active={!scheme.match(/system/)}
48
+ aria-label={scheme.match(/light/) ? 'Light mode, switch to dark mode' : scheme.match(/dark/) ? 'Dark mode, switch to light mode' : 'System mode, switch to system mode'}
49
+ borderRadius="full"
50
+ distinction="seamless"
51
+ onClick={useReferentiallyStableCallback(() => {
52
+ setScheme(scheme => (scheme.match(/light/) ? 'dark' : scheme.match(/dark/) ? 'system' : 'light'))
53
+ })}
54
+ size="small"
55
+ >
56
+ {scheme.match(/light/) ? <SunIcon /> : scheme.match(/dark/) ? <MoonIcon /> : <CircleDashedIcon />}
57
+ </Button>
58
+ </SlotSources.Switchers>
59
+
60
+ <SlotSources.Profile>
61
+ <LogoutLink>
62
+ <Stack align="center" horizontal gap="small">
63
+ <LogOutIcon /> Logout
64
+ </Stack>
65
+ </LogoutLink>
66
+ </SlotSources.Profile>
67
+
68
+ {children}
69
+ </LayoutComponent>
70
+ </PortalProvider>
71
+ </ColorSchemeProvider>
72
+ </SafeAreaInsetsProvider>
73
+ )
74
+ })
75
+ Layout.displayName = 'Layout'
@@ -0,0 +1,93 @@
1
+ import { LayoutKit, Slots, createLayoutBarComponent } from '@contember/layout'
2
+ import { Divider, Spacer, Stack } from '@contember/ui'
3
+ import { ComponentClassNameProps } from '@contember/utilities'
4
+ import { PropsWithChildren } from 'react'
5
+ import { AppHeaderTitle } from './AppHeaderTitle'
6
+ import { useDirectives } from './Directives'
7
+ import { PanelDivider } from './PanelDivider'
8
+ import { SlotTargets } from './Slots'
9
+
10
+ const SubHeader = createLayoutBarComponent({
11
+ defaultAs: 'div',
12
+ displayName: 'SubHeader',
13
+ name: 'sub-header',
14
+ })
15
+
16
+ export const LayoutComponent = ({ children, ...rest }: PropsWithChildren<ComponentClassNameProps>) => {
17
+ const directives = useDirectives()
18
+ const createSlotTargets = Slots.useTargetsIfActiveFactory(SlotTargets)
19
+
20
+ return (
21
+ <LayoutKit.Frame
22
+ header={
23
+ <>
24
+ <LayoutKit.Header
25
+ start={createSlotTargets(['Logo', 'HeaderStart']) || false}
26
+ center={createSlotTargets(['HeaderCenter']) || false}
27
+ end={state => {
28
+ const targets = createSlotTargets(['HeaderEnd', 'Profile'])
29
+ const menuButton = state.panels.get(LayoutKit.SidebarLeft.NAME)?.behavior === 'modal'
30
+ ? < LayoutKit.ToggleMenuButton panelName={LayoutKit.SidebarLeft.NAME} />
31
+ : null
32
+
33
+ return (targets || menuButton)
34
+ ? (
35
+ <>
36
+ {targets}
37
+ {menuButton}
38
+ </>
39
+ )
40
+ : false
41
+ }}
42
+ />
43
+ <SubHeader
44
+ start={createSlotTargets(['Back']) || false}
45
+ center={<SlotTargets.Title as={AppHeaderTitle} />}
46
+ end={createSlotTargets(['Actions']) || false}
47
+ />
48
+ </>
49
+ }
50
+ footer={(
51
+ <LayoutKit.Footer
52
+ start={createSlotTargets(['FooterStart']) || false}
53
+ center={createSlotTargets(['FooterCenter']) || false}
54
+ end={createSlotTargets(['Switchers', 'FooterEnd']) || false}
55
+ />
56
+ )}
57
+ {...rest}
58
+ >
59
+ <LayoutKit.SidebarLeft
60
+ keepVisible
61
+ header={(({ behavior }) => behavior === 'modal'
62
+ ? (
63
+ <>
64
+ <SlotTargets.SidebarLeftHeader />
65
+ <Spacer />
66
+ <LayoutKit.ToggleMenuButton panelName={LayoutKit.SidebarLeft.NAME} />
67
+ </>
68
+ )
69
+ : createSlotTargets(['SidebarLeftHeader'])
70
+ ) || false}
71
+ body={createSlotTargets(['Navigation', 'SidebarLeftBody']) || false}
72
+ footer={createSlotTargets(['SidebarLeftFooter']) || false}
73
+ />
74
+
75
+ <PanelDivider name={LayoutKit.SidebarLeft.NAME} />
76
+
77
+ <LayoutKit.ContentPanelMain
78
+ header={createSlotTargets(['ContentHeader'])}
79
+ body={(
80
+ <>
81
+ {children}
82
+ <SlotTargets.SidebarRightHeader />
83
+ <SlotTargets.Sidebar />
84
+ <SlotTargets.SidebarRightBody />
85
+ <SlotTargets.SidebarRightFooter />
86
+ </>)}
87
+ footer={createSlotTargets(['ContentFooter'])}
88
+ maxWidth={directives?.['content-max-width']}
89
+ />
90
+ </LayoutKit.Frame>
91
+ )
92
+ }
93
+ LayoutComponent.displayName = 'Layout(default)'
@@ -3,8 +3,6 @@ import { Menu } from '@contember/admin'
3
3
 
4
4
  export const Navigation = () => (
5
5
  <Menu>
6
- <Menu.Item>
7
- <Menu.Item title="Dashboard" to="index" />
8
- </Menu.Item>
6
+ <Menu.Item title="Dashboard" to="index" />
9
7
  </Menu>
10
8
  )
@@ -0,0 +1,14 @@
1
+ import { LayoutPrimitives } from '@contember/layout'
2
+ import { Divider } from '@contember/ui'
3
+ import { memo } from 'react'
4
+
5
+ export const PanelDivider = memo<{ name: string }>(({ name }) => (
6
+ <LayoutPrimitives.GetLayoutPanelsStateContext.Consumer>{({ panels }) => {
7
+ const panel = panels.get(name)
8
+
9
+ return panel?.behavior === 'static' && panel?.visibility === 'visible' && (
10
+ <Divider />
11
+ )
12
+ }}</LayoutPrimitives.GetLayoutPanelsStateContext.Consumer>
13
+ ))
14
+ PanelDivider.displayName = 'PanelDivider'
@@ -0,0 +1,64 @@
1
+ import {
2
+ CommonSlotSources,
3
+ CommonSlotTargets,
4
+ ContentSlotSources,
5
+ ContentSlotTargets,
6
+ FooterSlotSources,
7
+ FooterSlotTargets,
8
+ HeaderSlotSources,
9
+ HeaderSlotTargets,
10
+ SidebarLeftSlotSources,
11
+ SidebarLeftSlotTargets,
12
+ SidebarRightSlotSources,
13
+ SidebarRightSlotTargets,
14
+ commonSlots,
15
+ contentSlots,
16
+ footerSlots,
17
+ headerSlots,
18
+ sidebarLeftSlots,
19
+ sidebarRightSlots,
20
+ } from '@contember/layout'
21
+ import { useDocumentTitle } from '@contember/react-utils'
22
+ import { memo } from 'react'
23
+
24
+ const Title = memo<{ children: string | null | undefined }>(({ children }) => {
25
+ useDocumentTitle(children)
26
+
27
+ return (
28
+ <CommonSlotSources.Title>{children}</CommonSlotSources.Title>
29
+ )
30
+ })
31
+
32
+ export const slots = [
33
+ ...commonSlots,
34
+ ...contentSlots,
35
+ ...headerSlots,
36
+ ...footerSlots,
37
+ ...sidebarLeftSlots,
38
+ ...sidebarRightSlots,
39
+ // Your custom slot names will come here, e.g:
40
+ // 'MySlot',
41
+ ]
42
+
43
+ export const SlotSources = {
44
+ ...CommonSlotSources,
45
+ ...ContentSlotSources,
46
+ ...HeaderSlotSources,
47
+ ...FooterSlotSources,
48
+ ...SidebarLeftSlotSources,
49
+ ...SidebarRightSlotSources,
50
+ Title,
51
+ // Your custom slots will come here, e.g:
52
+ // MySLot: Slots.createSlotSourceComponent(slotTargets.MySLot),
53
+ }
54
+
55
+ export const SlotTargets = {
56
+ ...CommonSlotTargets,
57
+ ...ContentSlotTargets,
58
+ ...HeaderSlotTargets,
59
+ ...FooterSlotTargets,
60
+ ...SidebarLeftSlotTargets,
61
+ ...SidebarRightSlotTargets,
62
+ // Your custom slot targets will come here, e.g:
63
+ // MySLot: Slots.createSlotTargetComponent(slotTargets.MySLot),
64
+ }
@@ -1,15 +1,34 @@
1
1
  import * as React from 'react'
2
- import { ApplicationEntrypoint, Pages, runReactApp } from '@contember/admin'
3
- import '@contember/admin/style.css'
2
+ import { ApplicationEntrypoint, PageModule, Pages, runReactApp } from '@contember/admin'
3
+ import '@contember/admin/index.css'
4
+ import '@contember/layout/index.css'
5
+
4
6
  import { Layout } from './components/Layout'
7
+ import { createRoot } from 'react-dom/client'
8
+ import { Directives, Slots } from '@contember/layout'
9
+ import { initialDirectives } from './components/Directives'
5
10
 
6
11
  runReactApp(
7
- <ApplicationEntrypoint
8
- basePath={import.meta.env.BASE_URL}
9
- apiBaseUrl={import.meta.env.VITE_CONTEMBER_ADMIN_API_BASE_URL}
10
- sessionToken={import.meta.env.VITE_CONTEMBER_ADMIN_SESSION_TOKEN}
11
- project={import.meta.env.VITE_CONTEMBER_ADMIN_PROJECT_NAME}
12
- stage="live"
13
- children={<Pages layout={Layout} children={import.meta.globEager('./pages/**/*.tsx')} />}
14
- />,
12
+ <Directives.Provider value={initialDirectives}>
13
+ <Slots.Provider>
14
+ <ApplicationEntrypoint
15
+ basePath={import.meta.env.BASE_URL}
16
+ apiBaseUrl={import.meta.env.VITE_CONTEMBER_ADMIN_API_BASE_URL}
17
+ sessionToken={import.meta.env.VITE_CONTEMBER_ADMIN_SESSION_TOKEN}
18
+ project={import.meta.env.VITE_CONTEMBER_ADMIN_PROJECT_NAME}
19
+ stage="live"
20
+ children={
21
+ <Pages
22
+ layout={Layout}
23
+ children={import.meta.glob<PageModule>(
24
+ './pages/**/*.tsx',
25
+ { eager: true },
26
+ )}
27
+ />
28
+ }
29
+ />
30
+ </Slots.Provider>
31
+ </Directives.Provider>,
32
+ null,
33
+ (dom, react, onRecoverableError) => createRoot(dom, { onRecoverableError }).render(react),
15
34
  )
@@ -1,8 +1,7 @@
1
1
  import * as React from 'react'
2
- import { GenericPage } from '@contember/admin'
3
2
 
4
3
  export default () => (
5
- <GenericPage>
6
- Welcome to Contember Admin!
7
- </GenericPage>
4
+ <div>
5
+ Welcome to Contember Interface!
6
+ </div>
8
7
  )
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "isolatedModules": true,
4
- "jsx": "react",
4
+ "jsx": "react-jsx",
5
5
  "lib": ["esnext", "dom", "dom.iterable"],
6
6
  "module": "esnext",
7
7
  "moduleResolution": "node",
@@ -12,12 +12,14 @@
12
12
  "devDependencies": {
13
13
  "@contember/schema": "{version}",
14
14
  "@contember/schema-definition": "{version}",
15
- "@contember/admin": "^1.0.0",
15
+ "@contember/admin": "^1.2.6",
16
+ "@contember/layout": "^1.2.6",
16
17
  "@types/node": "^18",
17
- "@types/react": "^17",
18
- "react": "^17",
19
- "react-dom": "^17",
20
- "typescript": "^4.5",
21
- "vite": "^2.7"
18
+ "@types/react": "^18",
19
+ "@types/react-dom": "^18",
20
+ "react": "^18",
21
+ "react-dom": "^18",
22
+ "typescript": "^5.3.3",
23
+ "vite": "^4.5.1"
22
24
  }
23
25
  }