@umami/shiso 0.1.0

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.
Files changed (60) hide show
  1. package/.eslintrc.json +11 -0
  2. package/.prettierrc.json +7 -0
  3. package/LICENSE +21 -0
  4. package/README.md +7 -0
  5. package/dist/index.css +158 -0
  6. package/dist/index.d.ts +95 -0
  7. package/dist/index.js +335 -0
  8. package/dist/index.mjs +302 -0
  9. package/dts.mjs +8 -0
  10. package/esbuild.mjs +36 -0
  11. package/next.config.ts +15 -0
  12. package/package.json +83 -0
  13. package/public/leaf.png +0 -0
  14. package/public/leaf.svg +1 -0
  15. package/src/app/Footer.tsx +14 -0
  16. package/src/app/Header.tsx +14 -0
  17. package/src/app/blog/[[...id]]/page.tsx +8 -0
  18. package/src/app/docs/[[...id]]/page.tsx +8 -0
  19. package/src/app/layout.tsx +41 -0
  20. package/src/app/page.tsx +25 -0
  21. package/src/assets/leaf.svg +1 -0
  22. package/src/components/Footer.module.css +0 -0
  23. package/src/components/Footer.tsx +1 -0
  24. package/src/components/Header.module.css +0 -0
  25. package/src/components/Header.tsx +0 -0
  26. package/src/components/Markdown.tsx +21 -0
  27. package/src/components/Shiso.tsx +19 -0
  28. package/src/components/blog/Blog.module.css +0 -0
  29. package/src/components/blog/Blog.tsx +21 -0
  30. package/src/components/docs/Callout.module.css +6 -0
  31. package/src/components/docs/Callout.tsx +18 -0
  32. package/src/components/docs/CodeBlock.module.css +37 -0
  33. package/src/components/docs/CodeBlock.tsx +30 -0
  34. package/src/components/docs/ContentArea.module.css +63 -0
  35. package/src/components/docs/ContentArea.tsx +27 -0
  36. package/src/components/docs/Docs.tsx +50 -0
  37. package/src/components/docs/Note.tsx +5 -0
  38. package/src/components/docs/PageLinks.module.css +49 -0
  39. package/src/components/docs/PageLinks.tsx +49 -0
  40. package/src/components/docs/SideNav.module.css +32 -0
  41. package/src/components/docs/SideNav.tsx +46 -0
  42. package/src/components/docs/TopNav.tsx +25 -0
  43. package/src/components/icons/Leaf.tsx +15 -0
  44. package/src/components/icons/index.ts +1 -0
  45. package/src/components/index.ts +8 -0
  46. package/src/content/blog/index.mdx +5 -0
  47. package/src/content/docs/components/index.mdx +1 -0
  48. package/src/content/docs/components.mdx +5 -0
  49. package/src/content/docs/configuration.mdx +81 -0
  50. package/src/content/docs/index.mdx +11 -0
  51. package/src/content/docs/installation.mdx +38 -0
  52. package/src/content/docs/writing-content.mdx +95 -0
  53. package/src/declarations.d.ts +3 -0
  54. package/src/index.ts +5 -0
  55. package/src/lib/content.ts +61 -0
  56. package/src/lib/next.tsx +46 -0
  57. package/src/lib/types.ts +10 -0
  58. package/src/shiso.config.json +59 -0
  59. package/tsconfig.build.json +24 -0
  60. package/tsconfig.json +41 -0
@@ -0,0 +1,18 @@
1
+ import { ReactNode } from 'react';
2
+ import classNames from 'classnames';
3
+ import styles from './Callout.module.css';
4
+
5
+ export interface CalloutProps {
6
+ variant: 'note' | 'warning' | 'info' | 'tip';
7
+ icon?: ReactNode;
8
+ children: ReactNode;
9
+ }
10
+
11
+ export default function Callout({ variant = 'note', icon, children }: CalloutProps) {
12
+ return (
13
+ <div className={classNames(styles[variant])}>
14
+ {icon}
15
+ {children}
16
+ </div>
17
+ );
18
+ }
@@ -0,0 +1,37 @@
1
+ .container {
2
+ position: relative;
3
+ background: var(--base-color-900);
4
+ padding: var(--spacing-5);
5
+ border-radius: 6px;
6
+ white-space: pre-wrap;
7
+ word-wrap: break-word;
8
+ }
9
+
10
+ pre.container code {
11
+ font-family: var(--font-family-code);
12
+ color: var(--base-color-300);
13
+ padding: 0;
14
+ border: 0;
15
+ background: transparent;
16
+ }
17
+
18
+ .button {
19
+ border: 0;
20
+ position: absolute;
21
+ right: var(--spacing-5);
22
+ top: var(--spacing-5);
23
+ cursor: pointer;
24
+ background: transparent;
25
+ }
26
+
27
+ .copy {
28
+ color: var(--base-color-300);
29
+ }
30
+
31
+ .copy:hover {
32
+ color: var(--base-color-50);
33
+ }
34
+
35
+ .check {
36
+ color: var(--success-color);
37
+ }
@@ -0,0 +1,30 @@
1
+ 'use client';
2
+ import { useState, useRef } from 'react';
3
+ import { Icon, Icons } from '@umami/react-zen';
4
+ import styles from './CodeBlock.module.css';
5
+
6
+ export function CodeBlock(props: any) {
7
+ const textInput = useRef<any>(null);
8
+ const [copied, setCopied] = useState(false);
9
+
10
+ const handleCopy = () => {
11
+ setCopied(true);
12
+
13
+ navigator?.clipboard?.writeText(textInput?.current?.textContent);
14
+
15
+ setTimeout(() => {
16
+ setCopied(false);
17
+ }, 1000);
18
+ };
19
+
20
+ return (
21
+ <pre ref={textInput} className={styles.container}>
22
+ {props.children}
23
+ <button aria-label="Copy code" className={styles.button} onClick={handleCopy}>
24
+ <Icon size="sm" className={copied ? styles.check : styles.copy}>
25
+ {copied ? <Icons.Check /> : <Icons.Copy />}
26
+ </Icon>
27
+ </button>
28
+ </pre>
29
+ );
30
+ }
@@ -0,0 +1,63 @@
1
+ .content {
2
+ font-size: 16px;
3
+ margin-bottom: 60px;
4
+ }
5
+
6
+ .content > p {
7
+ color: var(--font-muted-color);
8
+ line-height: 1.8;
9
+ }
10
+
11
+ .content a {
12
+ color: var(--font-color);
13
+ font-weight: 600;
14
+ text-decoration: underline var(--font-faded-color);
15
+ text-underline-offset: 4px;
16
+ }
17
+
18
+ .content a:hover {
19
+ text-decoration: underline var(--font-color);
20
+ }
21
+
22
+ .content h1 {
23
+ font-size: var(--font-size-8);
24
+ letter-spacing: var(--letter-spacing-1);
25
+ margin: 0 0 var(--spacing-4) 0;
26
+ }
27
+
28
+ .content h2 {
29
+ font-size: var(--font-size-6);
30
+ letter-spacing: var(--letter-spacing-1);
31
+ margin: var(--spacing-8) 0 var(--spacing-4) 0;
32
+ }
33
+
34
+ .content h3 {
35
+ font-size: var(--font-size-5);
36
+ letter-spacing: var(--letter-spacing-1);
37
+ margin: var(--spacing-7) 0 var(--spacing-4) 0;
38
+ }
39
+
40
+ .content ul {
41
+ list-style: disc;
42
+ }
43
+
44
+ .content ol {
45
+ list-style: decimal;
46
+ }
47
+
48
+ .content pre {
49
+ margin: var(--spacing-5) 0;
50
+ }
51
+
52
+ .content code {
53
+ font-size: 14px;
54
+ }
55
+
56
+ .content code:not([class]) {
57
+ font-family: var(--font-family-code);
58
+ color: var(--font-color);
59
+ padding: var(--spacing-1);
60
+ border-radius: var(--border-radius);
61
+ background: var(--base-color-50);
62
+ font-weight: var(--font-weight-bold);
63
+ }
@@ -0,0 +1,27 @@
1
+ import { Heading, Box, Text } from '@umami/react-zen';
2
+ import Markdown from '@/components/Markdown';
3
+ import styles from './ContentArea.module.css';
4
+
5
+ export interface ContentAreaProps {
6
+ title: string;
7
+ body: string;
8
+ description?: string;
9
+ group?: string;
10
+ }
11
+
12
+ export function ContentArea({ title, description, group, body }: ContentAreaProps) {
13
+ return (
14
+ <Box flexGrow={1}>
15
+ <Text color="primary" weight="bold">
16
+ {group}
17
+ </Text>
18
+ <Heading size="8" as="h1">
19
+ {title}
20
+ </Heading>
21
+ <Text type="muted">{description}</Text>
22
+ <Box className={styles.content}>
23
+ <Markdown>{body}</Markdown>
24
+ </Box>
25
+ </Box>
26
+ );
27
+ }
@@ -0,0 +1,50 @@
1
+ import { Grid, Box, Heading } from '@umami/react-zen';
2
+ import PageLinks from './PageLinks';
3
+ import SideNav from './SideNav';
4
+ import TopNav from './TopNav';
5
+ import { ContentArea } from './ContentArea';
6
+ import { ShisoConfig } from '../Shiso';
7
+
8
+ export interface contentsProps {
9
+ config: ShisoConfig;
10
+ content: any;
11
+ }
12
+
13
+ export function Docs({ content, config }: contentsProps) {
14
+ if (!content) {
15
+ return <Heading size="6">Page not found</Heading>;
16
+ }
17
+
18
+ const { tabs, navigation } = config?.docs || {};
19
+
20
+ content.group = Object.keys(navigation).reduce((name, key) => {
21
+ if (!name) {
22
+ return navigation[key]?.find((section: { pages: any[] }) => {
23
+ return section.pages.find(
24
+ page =>
25
+ page.url === `/docs/${content.id}` || (page.url === `/docs` && content.id === 'index'),
26
+ );
27
+ })?.group;
28
+ }
29
+
30
+ return name;
31
+ }, null);
32
+
33
+ console.log({ config, tabs, content });
34
+
35
+ return (
36
+ <Box flexGrow="1">
37
+ {tabs && <TopNav tabs={tabs} />}
38
+ <Grid gap="6" columns="240px 1fr 240px">
39
+ <SideNav tabs={tabs} navigation={navigation} />
40
+ <ContentArea
41
+ title={content?.meta?.title}
42
+ body={content?.body}
43
+ group={content?.group}
44
+ description={content?.meta?.description}
45
+ />
46
+ <PageLinks items={content?.anchors} offset={150} />
47
+ </Grid>
48
+ </Box>
49
+ );
50
+ }
@@ -0,0 +1,5 @@
1
+ import Callout from './Callout';
2
+
3
+ export function Note({ children }: { children: React.ReactNode }) {
4
+ return <Callout variant="note">{children}</Callout>;
5
+ }
@@ -0,0 +1,49 @@
1
+ .title {
2
+ position: sticky;
3
+ top: 0;
4
+ }
5
+
6
+ .links {
7
+ position: sticky;
8
+ top: 30px;
9
+ height: max-content;
10
+ }
11
+
12
+ .links a {
13
+ display: block;
14
+ color: var(--font-muted-color);
15
+ text-wrap: nowrap;
16
+ text-decoration: none;
17
+ }
18
+
19
+ .links a:hover {
20
+ color: var(--primary-color);
21
+ }
22
+
23
+ .indent-1 {
24
+ margin-left: 0;
25
+ }
26
+
27
+ .indent-2 {
28
+ margin-left: 0;
29
+ }
30
+
31
+ .indent-3 {
32
+ margin-left: 10px;
33
+ }
34
+
35
+ .indent-4 {
36
+ margin-left: 20px;
37
+ }
38
+
39
+ .indent-5 {
40
+ margin-left: 30px;
41
+ }
42
+
43
+ .indent-6 {
44
+ margin-left: 40px;
45
+ }
46
+
47
+ a.selected {
48
+ color: var(--primary-color);
49
+ }
@@ -0,0 +1,49 @@
1
+ 'use client';
2
+ import { useEffect, useState } from 'react';
3
+ import { Column, Text } from '@umami/react-zen';
4
+ import classNames from 'classnames';
5
+ import styles from './PageLinks.module.css';
6
+
7
+ export default function PageLinks({ items, offset = 0 }) {
8
+ const [hash, setHash] = useState(items?.[0]?.id);
9
+
10
+ useEffect(() => {
11
+ const callback = () => {
12
+ const x = [...items].reverse().find(({ id }) => {
13
+ const rect = document.getElementById(id)?.getBoundingClientRect();
14
+ return rect && rect.top <= offset;
15
+ });
16
+
17
+ if (x) setHash(x.id);
18
+ };
19
+
20
+ window.addEventListener('scroll', callback, false);
21
+
22
+ return () => {
23
+ window.removeEventListener('scroll', callback, false);
24
+ };
25
+ }, []);
26
+
27
+ if (!items?.length) {
28
+ return null;
29
+ }
30
+
31
+ return (
32
+ <Column gap="3" minWidth="240px" className={styles.links}>
33
+ <Text size="2" weight="bold">
34
+ On this page
35
+ </Text>
36
+ {items.map(({ name, id, size }) => {
37
+ return (
38
+ <a
39
+ key={id}
40
+ href={`#${id}`}
41
+ className={classNames(styles[`indent-${size}`], { [styles.selected]: hash === id })}
42
+ >
43
+ {name}
44
+ </a>
45
+ );
46
+ })}
47
+ </Column>
48
+ );
49
+ }
@@ -0,0 +1,32 @@
1
+ .nav {
2
+ position: sticky;
3
+ top: 30px;
4
+ height: max-content;
5
+ }
6
+
7
+ .item {
8
+ color: var(--font-muted-color);
9
+ padding: 0;
10
+ border-radius: 0;
11
+ border-left: var(--border-size-1) solid var(--border-color);
12
+ }
13
+
14
+ .item a {
15
+ color: currentColor;
16
+ width: 100%;
17
+ padding: var(--padding-y) var(--padding-x);
18
+ text-decoration: none;
19
+ }
20
+
21
+ .item a:hover {
22
+ color: var(--primary-color);
23
+ }
24
+
25
+ .item.selected {
26
+ color: var(--font-color);
27
+ border-color: var(--primary-color);
28
+ }
29
+
30
+ .items header {
31
+ padding: var(--padding-y) 0;
32
+ }
@@ -0,0 +1,46 @@
1
+ 'use client';
2
+ import React from 'react';
3
+ import { Box, List, ListItem, ListSection } from '@umami/react-zen';
4
+ import classNames from 'classnames';
5
+ import { usePathname } from 'next/navigation';
6
+ import Link from 'next/link';
7
+ import styles from './SideNav.module.css';
8
+
9
+ export interface SideNavProps {
10
+ tabs: any;
11
+ navigation: any;
12
+ }
13
+
14
+ export default function SideNav({ tabs, navigation }: SideNavProps) {
15
+ const pathname = usePathname();
16
+
17
+ const tab = tabs.find(({ url, id }) => (id !== 'docs' ? pathname.startsWith(url) : false));
18
+ const menu = navigation[tab?.id || 'docs'];
19
+
20
+ return (
21
+ <Box className={styles.nav}>
22
+ <List items={navigation}>
23
+ {menu.map(({ group, pages }) => {
24
+ return (
25
+ <ListSection title={group} key={group} className={styles.items}>
26
+ {pages.map(({ label: text, url }, index: number) => {
27
+ return (
28
+ <ListItem
29
+ key={index}
30
+ className={classNames(styles.item, {
31
+ [styles.selected]: url === pathname,
32
+ })}
33
+ >
34
+ <Link href={url} prefetch={false}>
35
+ {text}
36
+ </Link>
37
+ </ListItem>
38
+ );
39
+ })}
40
+ </ListSection>
41
+ );
42
+ })}
43
+ </List>
44
+ </Box>
45
+ );
46
+ }
@@ -0,0 +1,25 @@
1
+ 'use client';
2
+ import Link from 'next/link';
3
+ import { usePathname } from 'next/navigation';
4
+ import { Tabs, TabList, Tab } from '@umami/react-zen';
5
+
6
+ export default function TopNav({ tabs }) {
7
+ const pathname = usePathname();
8
+
9
+ const tab = tabs.find(({ id, url }) => (id !== 'docs' ? pathname.startsWith(url) : false));
10
+ const selected = tab?.id || 'docs';
11
+
12
+ return (
13
+ <Tabs selectedKey={selected}>
14
+ <TabList items={tabs}>
15
+ {({ id, label, url }) => {
16
+ return (
17
+ <Tab id={id}>
18
+ <Link href={url}>{label}</Link>
19
+ </Tab>
20
+ );
21
+ }}
22
+ </TabList>
23
+ </Tabs>
24
+ );
25
+ }
@@ -0,0 +1,15 @@
1
+ import * as React from 'react';
2
+ import type { SVGProps } from 'react';
3
+ const SvgLeaf = (props: SVGProps<SVGSVGElement>) => (
4
+ <svg
5
+ xmlns="http://www.w3.org/2000/svg"
6
+ width={512}
7
+ height={512}
8
+ data-name="Glyph Icon"
9
+ viewBox="0 0 64 64"
10
+ {...props}
11
+ >
12
+ <path d="M55.221 10.79a1.31 1.31 0 0 0-1.564.39c-4.738 5.882-18.607 6.77-22.746 6.903a18.3 18.3 0 0 0-3.067.366C7.277 22.636 7.589 37.763 8.173 42.299c.097.75.601 1.362 1.315 1.599a1.91 1.91 0 0 0 1.988-.492c2.403-2.49 7.671-6.969 15.952-8.985 3.422-.833 6.668-2.36 9.557-4.111-3.375 2.564-7.731 5.332-13.243 7.92-5.526 2.596-9.586 7.096-11.075 8.909a1.94 1.94 0 0 0-.423 1.483c.072.536.368 1.023.812 1.338 1.414 1.004 3.099 1.813 5.006 2.408 2.053.638 4.168.954 6.286.954 4.28 0 8.564-1.287 12.329-3.782 14.464-9.588 18.439-31.031 19.312-37.373a1.29 1.29 0 0 0-.768-1.377" />
13
+ </svg>
14
+ );
15
+ export default SvgLeaf;
@@ -0,0 +1 @@
1
+ export { default as Leaf } from './Leaf';
@@ -0,0 +1,8 @@
1
+ export * from './blog/Blog';
2
+ export * from './docs/Callout';
3
+ export * from './docs/CodeBlock';
4
+ export * from './docs/Docs';
5
+ export * from './docs/SideNav';
6
+ export * from './docs/Note';
7
+ export * from './docs/PageLinks';
8
+ export * from './docs/TopNav';
@@ -0,0 +1,5 @@
1
+ ---
2
+ title: Blog
3
+ ---
4
+
5
+ ## This is a blog post
@@ -0,0 +1 @@
1
+ # Components
@@ -0,0 +1,5 @@
1
+ ---
2
+ title: Components
3
+ ---
4
+
5
+ All shiso components.
@@ -0,0 +1,81 @@
1
+ ---
2
+ title: Configuration
3
+ ---
4
+
5
+ ## Config file
6
+
7
+ The `shiso.config.json` file contains the configuration options for your project.
8
+
9
+ The file has the following schema:
10
+
11
+ ```json
12
+ {
13
+ "docs": {},
14
+ "blog": {}
15
+ }
16
+ ```
17
+
18
+ At the root level, the properties represent the different content sections of your project.
19
+ Shiso currently supports `docs` and `blog` content.
20
+
21
+ ## Docs options
22
+
23
+ ```json
24
+ {
25
+ "docs": {
26
+ "tabs": [
27
+ {
28
+ "id": "docs",
29
+ "label": "Documentation",
30
+ "url": "/docs"
31
+ },
32
+ {
33
+ "id": "components",
34
+ "label": "Components",
35
+ "url": "/docs/components"
36
+ }
37
+ ],
38
+ "navigation": {
39
+ "docs": [
40
+ {
41
+ "group": "Getting started",
42
+ "pages": [
43
+ {
44
+ "label": "Overview",
45
+ "url": "/docs"
46
+ },
47
+ {
48
+ "label": "Installation",
49
+ "url": "/docs/installation"
50
+ }
51
+ ]
52
+ },
53
+ {
54
+ "group": "Basics",
55
+ "pages": [
56
+ {
57
+ "label": "Writing content",
58
+ "url": "/docs/writing-content"
59
+ }
60
+ ]
61
+ }
62
+ ],
63
+ "components": [
64
+ {
65
+ "group": "Components",
66
+ "pages": [
67
+ {
68
+ "label": "Overview",
69
+ "url": "/docs/components"
70
+ }
71
+ ]
72
+ }
73
+ ]
74
+ }
75
+ }
76
+ }
77
+ ```
78
+
79
+ ## Blog options
80
+
81
+ ...
@@ -0,0 +1,11 @@
1
+ ---
2
+ title: Overview
3
+ ---
4
+
5
+ ## What is Shiso?
6
+
7
+ Shiso lets you quickly build websites by focusing on writing content. Shiso takes care of the
8
+ design, layout and functionality while you provide the content through Markdown files. You can also
9
+ embed React components into your content with MDX.
10
+
11
+ Out of the box, Shiso provides templates for docs and blogs and works with [Next.js](/docs/).
@@ -0,0 +1,38 @@
1
+ ---
2
+ title: Installation
3
+ ---
4
+
5
+ These instructions show how to set up Shiso in a Next.js project.
6
+
7
+ ## 1. Install the package
8
+
9
+ ```shell
10
+ npm install @umami/shiso
11
+ ```
12
+
13
+ ## 2. Create page
14
+
15
+ In your `app` folder, create a folder for the content section you wish to add.
16
+
17
+ ```text
18
+ src
19
+ ├── app
20
+ │ └── docs
21
+ │ └── [[...id]]
22
+ │ └── page.jsx
23
+ ```
24
+
25
+ In this case we are creating a section for `docs`.
26
+
27
+ In the `page.jsx` file, add the following code:
28
+
29
+ ```javascript
30
+ import shiso from '@umami/shiso/next';
31
+ import config from '@/shiso.config.json';
32
+
33
+ const { getMetadata, renderPage } = shiso(config);
34
+
35
+ export const generateMetadata = getMetadata('SHISO');
36
+
37
+ export default renderPage('docs');
38
+ ```