@tuwaio/docs-ui 0.1.1 → 0.3.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.
- package/README.md +44 -3
- package/dist/components/Footer.cjs +38 -8
- package/dist/components/Footer.d.cts +3 -2
- package/dist/components/Footer.d.ts +3 -2
- package/dist/components/Footer.js +38 -8
- package/dist/components/PolicyDocument.cjs +163 -0
- package/dist/components/PolicyDocument.d.cts +57 -0
- package/dist/components/PolicyDocument.d.ts +57 -0
- package/dist/components/PolicyDocument.js +129 -0
- package/dist/index.cjs +4 -0
- package/dist/index.css +218 -6
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/policies.cjs +124 -0
- package/dist/policies.d.cts +88 -0
- package/dist/policies.d.ts +88 -0
- package/dist/policies.js +92 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -3,16 +3,17 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@tuwaio/docs-ui)
|
|
4
4
|
[](https://github.com/TuwaIO/docs/blob/main/packages/docs-ui/LICENSE)
|
|
5
5
|
|
|
6
|
-
`@tuwaio/docs-ui` is the shared UI of the TUWA documentation sites: the navbar, footer, logo and stylesheet of every `*.docs.tuwa.io` site and of the [docs hub](https://docs.tuwa.io), plus the decorative parts of the hub and the TUWA landing page. The sites are built with Next.js (App Router) and Nextra 4; one package keeps them visually consistent.
|
|
6
|
+
`@tuwaio/docs-ui` is the shared UI of the TUWA documentation sites: the navbar, footer, logo and stylesheet of every `*.docs.tuwa.io` site and of the [docs hub](https://docs.tuwa.io), plus the decorative parts of the hub and the TUWA landing page, and the policy pages of the TUWA website and the Quasar dashboard. The sites are built with Next.js (App Router) and Nextra 4; one package keeps them visually consistent.
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
## 🏛️ Core Capabilities
|
|
11
11
|
|
|
12
|
-
- **Nextra layout parts:** `Navbar` and `Footer` wrap the navbar and footer of `nextra-theme-docs` with the site logo and a row of external links (`baseNavLinks`, `baseFooterLinks` are the defaults: the npm and GitHub organizations of TUWA).
|
|
12
|
+
- **Nextra layout parts:** `Navbar` and `Footer` wrap the navbar and footer of `nextra-theme-docs` with the site logo and a row of external links (`baseNavLinks`, `baseFooterLinks` are the defaults: the npm and GitHub organizations of TUWA). The footer also links the policies in force on tuwa.io and the list of all policies on the docs hub.
|
|
13
13
|
- **Logo:** `RemoteLogo` is an async React Server Component that fetches an SVG (by default the TUWA logo) and renders it as React elements, so it takes `className` and `currentColor`.
|
|
14
14
|
- **Theme switch:** `ThemeSwitcher` is a light/dark switch for any theme state (for example `next-themes`).
|
|
15
15
|
- **Decoration:** `Orb` (an animated sphere in the brand color of a TUWA project), `StarryBackground` (an animated star field on a canvas) and `NoSSR` (renders its children only on the client).
|
|
16
|
+
- **Policy pages:** `policies` lists the TUWA policies (Terms, Privacy, Cookie, Refund, Security, Data Processing Addendum) with their paths and whether they are in force (`publishedPolicies`; footers use `footerPolicies`). `loadPolicy` loads the text of a policy, and `PolicyDocument` renders it with the date of the version, a Download PDF button, anchored sections and a list of sections. The texts and PDFs come from [`TuwaIO/workflows/docs`](https://github.com/TuwaIO/workflows/tree/main/docs).
|
|
16
17
|
- **Stylesheet:** `dist/index.css` contains the `--tuwa-*` theme variables of [`@tuwaio/nova-core`](https://stories.tuwa.io/?path=/docs/packages-nova-core-overview--docs) (light and `.dark`), the styles of the Nextra parts and the Tailwind CSS v4 utilities used by the components, prefixed with `tuwadocs:`.
|
|
17
18
|
|
|
18
19
|
---
|
|
@@ -76,13 +77,53 @@ export default async function RootLayout({ children }: { children: ReactNode })
|
|
|
76
77
|
|
|
77
78
|
---
|
|
78
79
|
|
|
80
|
+
### Policy pages
|
|
81
|
+
|
|
82
|
+
A policy page in the Next.js App Router (a Server Component; the content is cached for an hour):
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
import { loadPolicy, PolicyDocument } from '@tuwaio/docs-ui';
|
|
86
|
+
|
|
87
|
+
export default async function RefundPage() {
|
|
88
|
+
const content = await loadPolicy('refund');
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<main className="mx-auto max-w-6xl px-4 py-12">
|
|
92
|
+
<PolicyDocument slug="refund" content={content} />
|
|
93
|
+
</main>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Footers list the policies in force:
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
import { footerPolicies } from '@tuwaio/docs-ui';
|
|
102
|
+
|
|
103
|
+
export function PolicyLinks() {
|
|
104
|
+
return (
|
|
105
|
+
<nav aria-label="Policies" className="flex flex-wrap gap-x-4 gap-y-2 text-xs">
|
|
106
|
+
{footerPolicies.map((policy) => (
|
|
107
|
+
<a key={policy.slug} href={policy.href}>
|
|
108
|
+
{policy.title}
|
|
109
|
+
</a>
|
|
110
|
+
))}
|
|
111
|
+
</nav>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Sections have stable anchors (`/refund#how-to-request-a-refund`), so hints and FAQ answers can link to a section.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
79
120
|
## 🗄️ Browser Storage
|
|
80
121
|
|
|
81
122
|
Nothing. `ThemeSwitcher` only calls `onToggle`; the site keeps the theme.
|
|
82
123
|
|
|
83
124
|
## 🌐 External Services
|
|
84
125
|
|
|
85
|
-
`RemoteLogo` requests its `url` on the server (by default `cdn.jsdelivr.net/gh/TuwaIO/workflows@main/preview/logo_v2.svg`), cached by Next.js for 24 hours. The other components send no requests.
|
|
126
|
+
`RemoteLogo` requests its `url` on the server (by default `cdn.jsdelivr.net/gh/TuwaIO/workflows@main/preview/logo_v2.svg`), cached by Next.js for 24 hours. `loadPolicy` requests `raw.githubusercontent.com/TuwaIO/workflows/main/docs/<slug>.json` on the server, cached by Next.js for an hour by default. The Download PDF button of `PolicyDocument` links to the PDF on the same host; the browser requests it only when it is clicked. The other components send no requests.
|
|
86
127
|
|
|
87
128
|
---
|
|
88
129
|
|
|
@@ -24,6 +24,7 @@ module.exports = __toCommonJS(Footer_exports);
|
|
|
24
24
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
25
25
|
var import_nova_core = require("@tuwaio/nova-core");
|
|
26
26
|
var import_nextra_theme_docs = require("nextra-theme-docs");
|
|
27
|
+
var import_policies = require("../policies");
|
|
27
28
|
var import_utils = require("../utils");
|
|
28
29
|
function Footer({ links, logo }) {
|
|
29
30
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_nextra_theme_docs.Footer, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tuwadocs:flex tuwadocs:w-full tuwadocs:flex-col tuwadocs:items-center tuwadocs:sm:items-start tuwa-footer-border tuwadocs:pt-8", children: [
|
|
@@ -45,18 +46,47 @@ function Footer({ links, logo }) {
|
|
|
45
46
|
)) })
|
|
46
47
|
] }),
|
|
47
48
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tuwadocs:w-full tuwa-footer-border tuwadocs:pt-4", children: [
|
|
48
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tuwadocs:flex tuwadocs:flex-col tuwadocs:sm:flex-row tuwadocs:justify-between tuwadocs:items-
|
|
49
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tuwadocs:flex-1", children: [
|
|
49
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tuwadocs:flex tuwadocs:flex-col tuwadocs:sm:flex-row tuwadocs:justify-between tuwadocs:items-center tuwadocs:sm:items-start tuwadocs:gap-4", children: [
|
|
50
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tuwadocs:flex-1 tuwadocs:text-center tuwadocs:sm:text-left", children: [
|
|
50
51
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "tuwa-footer-description", children: "The modular, headless-first Web3 infrastructure. Build self-custodial applications on open-source, portable building blocks." }),
|
|
51
52
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "tuwa-footer-license", children: "Licensed under Apache 2.0. Open source and free to use." })
|
|
52
53
|
] }),
|
|
53
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
55
|
+
"nav",
|
|
56
|
+
{
|
|
57
|
+
"aria-label": "Policies",
|
|
58
|
+
className: "tuwadocs:flex tuwadocs:flex-wrap tuwadocs:items-center tuwadocs:justify-center tuwadocs:sm:justify-end tuwadocs:gap-x-4 tuwadocs:gap-y-2 tuwadocs:text-xs",
|
|
59
|
+
children: [
|
|
60
|
+
import_policies.footerPolicies.map((policy) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
61
|
+
"a",
|
|
62
|
+
{
|
|
63
|
+
href: (0, import_policies.getPolicyPageUrl)(policy.slug),
|
|
64
|
+
target: "_blank",
|
|
65
|
+
rel: "noopener noreferrer",
|
|
66
|
+
className: "tuwa-footer-link",
|
|
67
|
+
children: policy.title
|
|
68
|
+
},
|
|
69
|
+
policy.slug
|
|
70
|
+
)),
|
|
71
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
72
|
+
"a",
|
|
73
|
+
{
|
|
74
|
+
href: import_policies.POLICIES_INDEX_URL,
|
|
75
|
+
target: "_blank",
|
|
76
|
+
rel: "noopener noreferrer",
|
|
77
|
+
className: "tuwa-footer-link tuwadocs:font-semibold",
|
|
78
|
+
children: "All policies"
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
)
|
|
58
84
|
] }),
|
|
59
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tuwadocs:flex tuwadocs:flex-wrap tuwadocs:items-center tuwadocs:gap-3 tuwadocs:mt-4 tuwadocs:pt-4 tuwa-footer-border"
|
|
85
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tuwadocs:flex tuwadocs:flex-wrap tuwadocs:items-center tuwadocs:justify-center tuwadocs:gap-3 tuwadocs:mt-4 tuwadocs:pt-4 tuwa-footer-border", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tuwa-footer-copyright", children: [
|
|
86
|
+
"\xA9 2025 - ",
|
|
87
|
+
(/* @__PURE__ */ new Date()).getFullYear(),
|
|
88
|
+
" TUWA. All rights reserved."
|
|
89
|
+
] }) })
|
|
60
90
|
] })
|
|
61
91
|
] }) });
|
|
62
92
|
}
|
|
@@ -3,8 +3,9 @@ import { NavProps } from './NavBar.cjs';
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* The footer of a TUWA documentation site: the `Footer` of `nextra-theme-docs` with the logo, external links, the
|
|
6
|
-
* TUWA description
|
|
7
|
-
*
|
|
6
|
+
* TUWA description and the license on the left, links to the policies in force on tuwa.io (`footerPolicies`) and to
|
|
7
|
+
* the list of all policies on the docs hub on the right, and the copyright line (the current year is read on render)
|
|
8
|
+
* centered below; on narrow screens everything is centered. Render it in the `footer` prop of the Nextra `Layout`.
|
|
8
9
|
*
|
|
9
10
|
* @param props - See {@link NavProps}.
|
|
10
11
|
* @returns The footer.
|
|
@@ -3,8 +3,9 @@ import { NavProps } from './NavBar.js';
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* The footer of a TUWA documentation site: the `Footer` of `nextra-theme-docs` with the logo, external links, the
|
|
6
|
-
* TUWA description
|
|
7
|
-
*
|
|
6
|
+
* TUWA description and the license on the left, links to the policies in force on tuwa.io (`footerPolicies`) and to
|
|
7
|
+
* the list of all policies on the docs hub on the right, and the copyright line (the current year is read on render)
|
|
8
|
+
* centered below; on narrow screens everything is centered. Render it in the `footer` prop of the Nextra `Layout`.
|
|
8
9
|
*
|
|
9
10
|
* @param props - See {@link NavProps}.
|
|
10
11
|
* @returns The footer.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "@tuwaio/nova-core";
|
|
3
3
|
import { Footer as NextraFooter } from "nextra-theme-docs";
|
|
4
|
+
import { footerPolicies, getPolicyPageUrl, POLICIES_INDEX_URL } from "../policies";
|
|
4
5
|
import { baseFooterLinks } from "../utils";
|
|
5
6
|
function Footer({ links, logo }) {
|
|
6
7
|
return /* @__PURE__ */ jsx(NextraFooter, { children: /* @__PURE__ */ jsxs("div", { className: "tuwadocs:flex tuwadocs:w-full tuwadocs:flex-col tuwadocs:items-center tuwadocs:sm:items-start tuwa-footer-border tuwadocs:pt-8", children: [
|
|
@@ -22,18 +23,47 @@ function Footer({ links, logo }) {
|
|
|
22
23
|
)) })
|
|
23
24
|
] }),
|
|
24
25
|
/* @__PURE__ */ jsxs("div", { className: "tuwadocs:w-full tuwa-footer-border tuwadocs:pt-4", children: [
|
|
25
|
-
/* @__PURE__ */ jsxs("div", { className: "tuwadocs:flex tuwadocs:flex-col tuwadocs:sm:flex-row tuwadocs:justify-between tuwadocs:items-
|
|
26
|
-
/* @__PURE__ */ jsxs("div", { className: "tuwadocs:flex-1", children: [
|
|
26
|
+
/* @__PURE__ */ jsxs("div", { className: "tuwadocs:flex tuwadocs:flex-col tuwadocs:sm:flex-row tuwadocs:justify-between tuwadocs:items-center tuwadocs:sm:items-start tuwadocs:gap-4", children: [
|
|
27
|
+
/* @__PURE__ */ jsxs("div", { className: "tuwadocs:flex-1 tuwadocs:text-center tuwadocs:sm:text-left", children: [
|
|
27
28
|
/* @__PURE__ */ jsx("p", { className: "tuwa-footer-description", children: "The modular, headless-first Web3 infrastructure. Build self-custodial applications on open-source, portable building blocks." }),
|
|
28
29
|
/* @__PURE__ */ jsx("p", { className: "tuwa-footer-license", children: "Licensed under Apache 2.0. Open source and free to use." })
|
|
29
30
|
] }),
|
|
30
|
-
/* @__PURE__ */ jsxs(
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
/* @__PURE__ */ jsxs(
|
|
32
|
+
"nav",
|
|
33
|
+
{
|
|
34
|
+
"aria-label": "Policies",
|
|
35
|
+
className: "tuwadocs:flex tuwadocs:flex-wrap tuwadocs:items-center tuwadocs:justify-center tuwadocs:sm:justify-end tuwadocs:gap-x-4 tuwadocs:gap-y-2 tuwadocs:text-xs",
|
|
36
|
+
children: [
|
|
37
|
+
footerPolicies.map((policy) => /* @__PURE__ */ jsx(
|
|
38
|
+
"a",
|
|
39
|
+
{
|
|
40
|
+
href: getPolicyPageUrl(policy.slug),
|
|
41
|
+
target: "_blank",
|
|
42
|
+
rel: "noopener noreferrer",
|
|
43
|
+
className: "tuwa-footer-link",
|
|
44
|
+
children: policy.title
|
|
45
|
+
},
|
|
46
|
+
policy.slug
|
|
47
|
+
)),
|
|
48
|
+
/* @__PURE__ */ jsx(
|
|
49
|
+
"a",
|
|
50
|
+
{
|
|
51
|
+
href: POLICIES_INDEX_URL,
|
|
52
|
+
target: "_blank",
|
|
53
|
+
rel: "noopener noreferrer",
|
|
54
|
+
className: "tuwa-footer-link tuwadocs:font-semibold",
|
|
55
|
+
children: "All policies"
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
)
|
|
35
61
|
] }),
|
|
36
|
-
/* @__PURE__ */ jsx("div", { className: "tuwadocs:flex tuwadocs:flex-wrap tuwadocs:items-center tuwadocs:gap-3 tuwadocs:mt-4 tuwadocs:pt-4 tuwa-footer-border"
|
|
62
|
+
/* @__PURE__ */ jsx("div", { className: "tuwadocs:flex tuwadocs:flex-wrap tuwadocs:items-center tuwadocs:justify-center tuwadocs:gap-3 tuwadocs:mt-4 tuwadocs:pt-4 tuwa-footer-border", children: /* @__PURE__ */ jsxs("div", { className: "tuwa-footer-copyright", children: [
|
|
63
|
+
"\xA9 2025 - ",
|
|
64
|
+
(/* @__PURE__ */ new Date()).getFullYear(),
|
|
65
|
+
" TUWA. All rights reserved."
|
|
66
|
+
] }) })
|
|
37
67
|
] })
|
|
38
68
|
] }) });
|
|
39
69
|
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var PolicyDocument_exports = {};
|
|
30
|
+
__export(PolicyDocument_exports, {
|
|
31
|
+
PolicyDocument: () => PolicyDocument
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(PolicyDocument_exports);
|
|
34
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
35
|
+
var import_outline = require("@heroicons/react/24/outline");
|
|
36
|
+
var import_nova_core = require("@tuwaio/nova-core");
|
|
37
|
+
var import_html_react_parser = __toESM(require("html-react-parser"), 1);
|
|
38
|
+
var import_policies = require("../policies");
|
|
39
|
+
const BLOCKED_ELEMENTS = /* @__PURE__ */ new Set([
|
|
40
|
+
"script",
|
|
41
|
+
"style",
|
|
42
|
+
"iframe",
|
|
43
|
+
"object",
|
|
44
|
+
"embed",
|
|
45
|
+
"form",
|
|
46
|
+
"input",
|
|
47
|
+
"button",
|
|
48
|
+
"textarea",
|
|
49
|
+
"select",
|
|
50
|
+
"link",
|
|
51
|
+
"meta",
|
|
52
|
+
"base"
|
|
53
|
+
]);
|
|
54
|
+
function sanitize(node) {
|
|
55
|
+
if (!(node instanceof import_html_react_parser.Element)) return void 0;
|
|
56
|
+
if (BLOCKED_ELEMENTS.has(node.name)) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, {});
|
|
57
|
+
for (const name of Object.keys(node.attribs)) {
|
|
58
|
+
if (name.toLowerCase().startsWith("on")) delete node.attribs[name];
|
|
59
|
+
}
|
|
60
|
+
if (node.name === "a") {
|
|
61
|
+
const href = node.attribs.href ?? "";
|
|
62
|
+
if (/^\s*javascript:/i.test(href)) {
|
|
63
|
+
delete node.attribs.href;
|
|
64
|
+
} else if (/^https?:\/\//i.test(href)) {
|
|
65
|
+
node.attribs.target = "_blank";
|
|
66
|
+
node.attribs.rel = "noopener noreferrer";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return void 0;
|
|
70
|
+
}
|
|
71
|
+
function PolicyDocument({
|
|
72
|
+
slug,
|
|
73
|
+
content,
|
|
74
|
+
source = import_policies.POLICY_SOURCE,
|
|
75
|
+
labels,
|
|
76
|
+
className,
|
|
77
|
+
classNames
|
|
78
|
+
}) {
|
|
79
|
+
const tocLabel = labels?.toc ?? "On this page";
|
|
80
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
81
|
+
"div",
|
|
82
|
+
{
|
|
83
|
+
className: (0, import_nova_core.cn)(
|
|
84
|
+
"tuwadocs:grid tuwadocs:grid-cols-1 tuwadocs:gap-10 tuwadocs:lg:grid-cols-[minmax(0,1fr)_14rem]",
|
|
85
|
+
className
|
|
86
|
+
),
|
|
87
|
+
children: [
|
|
88
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tuwadocs:min-w-0", children: [
|
|
89
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
90
|
+
"header",
|
|
91
|
+
{
|
|
92
|
+
className: (0, import_nova_core.cn)(
|
|
93
|
+
"tuwadocs:mb-8 tuwadocs:flex tuwadocs:flex-col tuwadocs:gap-4 tuwadocs:border-b tuwadocs:border-[var(--tuwa-border-primary)] tuwadocs:pb-6",
|
|
94
|
+
classNames?.header
|
|
95
|
+
),
|
|
96
|
+
children: [
|
|
97
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
98
|
+
"h1",
|
|
99
|
+
{
|
|
100
|
+
className: (0, import_nova_core.cn)(
|
|
101
|
+
"tuwadocs-policy-title tuwadocs:text-3xl tuwadocs:font-extrabold tuwadocs:tracking-tight tuwadocs:text-[var(--tuwa-text-primary)] tuwadocs:sm:text-4xl",
|
|
102
|
+
classNames?.title
|
|
103
|
+
),
|
|
104
|
+
children: content.title
|
|
105
|
+
}
|
|
106
|
+
),
|
|
107
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tuwadocs:flex tuwadocs:flex-wrap tuwadocs:items-center tuwadocs:justify-between tuwadocs:gap-3", children: [
|
|
108
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: (0, import_nova_core.cn)("tuwadocs:text-sm tuwadocs:text-[var(--tuwa-text-secondary)]", classNames?.updated), children: [
|
|
109
|
+
labels?.updated ?? "Last updated",
|
|
110
|
+
" ",
|
|
111
|
+
content.updated
|
|
112
|
+
] }),
|
|
113
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
114
|
+
"a",
|
|
115
|
+
{
|
|
116
|
+
href: (0, import_policies.getPolicyFileUrl)(slug, "pdf", source),
|
|
117
|
+
target: "_blank",
|
|
118
|
+
rel: "noopener noreferrer",
|
|
119
|
+
className: (0, import_nova_core.cn)(
|
|
120
|
+
"tuwadocs-policy-title tuwadocs:inline-flex tuwadocs:h-10 tuwadocs:no-underline tuwadocs:items-center tuwadocs:gap-2 tuwadocs:rounded-[var(--tuwa-rounded-corners)] tuwadocs:border tuwadocs:border-[var(--tuwa-border-primary)] tuwadocs:bg-[var(--tuwa-bg-primary)] tuwadocs:px-4 tuwadocs:text-sm tuwadocs:font-bold tuwadocs:text-[var(--tuwa-text-primary)] tuwadocs:transition-colors tuwadocs:hover:bg-[var(--tuwa-bg-muted)]",
|
|
121
|
+
classNames?.download
|
|
122
|
+
),
|
|
123
|
+
children: [
|
|
124
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_outline.ArrowDownTrayIcon, { className: "tuwadocs:h-4 tuwadocs:w-4", "aria-hidden": "true" }),
|
|
125
|
+
labels?.download ?? "Download PDF"
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
)
|
|
129
|
+
] })
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
),
|
|
133
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("article", { className: (0, import_nova_core.cn)("tuwadocs-policy", classNames?.body), children: (0, import_html_react_parser.default)(content.html, { replace: sanitize }) })
|
|
134
|
+
] }),
|
|
135
|
+
content.sections.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
136
|
+
"nav",
|
|
137
|
+
{
|
|
138
|
+
"aria-label": tocLabel,
|
|
139
|
+
className: (0, import_nova_core.cn)(
|
|
140
|
+
"tuwadocs:hidden tuwadocs:lg:sticky tuwadocs:lg:top-24 tuwadocs:lg:block tuwadocs:lg:self-start",
|
|
141
|
+
classNames?.toc
|
|
142
|
+
),
|
|
143
|
+
children: [
|
|
144
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "tuwadocs:mb-3 tuwadocs:text-xs tuwadocs:font-bold tuwadocs:uppercase tuwadocs:tracking-widest tuwadocs:text-[var(--tuwa-text-tertiary)]", children: tocLabel }),
|
|
145
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { className: "tuwadocs:flex tuwadocs:list-none tuwadocs:flex-col tuwadocs:gap-2 tuwadocs:border-l tuwadocs:border-[var(--tuwa-border-primary)] tuwadocs:pl-4", children: content.sections.map((section) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
146
|
+
"a",
|
|
147
|
+
{
|
|
148
|
+
href: `#${section.id}`,
|
|
149
|
+
className: "tuwadocs:text-sm tuwadocs:text-[var(--tuwa-text-secondary)] tuwadocs:no-underline tuwadocs:transition-colors tuwadocs:hover:text-[var(--tuwa-text-primary)]",
|
|
150
|
+
children: section.title
|
|
151
|
+
}
|
|
152
|
+
) }, section.id)) })
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
161
|
+
0 && (module.exports = {
|
|
162
|
+
PolicyDocument
|
|
163
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { PolicySlug, PolicyContent } from '../policies.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Props of {@link PolicyDocument}.
|
|
6
|
+
*/
|
|
7
|
+
interface PolicyDocumentProps {
|
|
8
|
+
/** Slug of the policy; sets the link of the Download PDF button. */
|
|
9
|
+
slug: PolicySlug;
|
|
10
|
+
/** Content of the policy, from `loadPolicy`. */
|
|
11
|
+
content: PolicyContent;
|
|
12
|
+
/** Base URL of the policy files for the PDF link (default: `POLICY_SOURCE`). */
|
|
13
|
+
source?: string;
|
|
14
|
+
/** Texts of the page. */
|
|
15
|
+
labels?: {
|
|
16
|
+
/** Text before the date of the version (default: `Last updated`). */
|
|
17
|
+
updated?: string;
|
|
18
|
+
/** Text of the button that opens the PDF (default: `Download PDF`). */
|
|
19
|
+
download?: string;
|
|
20
|
+
/** Heading and accessible name of the list of sections (default: `On this page`). */
|
|
21
|
+
toc?: string;
|
|
22
|
+
};
|
|
23
|
+
/** Classes of the root element. */
|
|
24
|
+
className?: string;
|
|
25
|
+
/** Classes of the parts of the page. */
|
|
26
|
+
classNames?: {
|
|
27
|
+
/** The header with the title, the date and the button. */
|
|
28
|
+
header?: string;
|
|
29
|
+
/** The title. */
|
|
30
|
+
title?: string;
|
|
31
|
+
/** The date of the version. */
|
|
32
|
+
updated?: string;
|
|
33
|
+
/** The Download PDF button. */
|
|
34
|
+
download?: string;
|
|
35
|
+
/** The `article` with the body. */
|
|
36
|
+
body?: string;
|
|
37
|
+
/** The list of sections (shown from the `lg` breakpoint). */
|
|
38
|
+
toc?: string;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A TUWA policy page (Terms, Privacy, Cookie, Refund, Security): the title, the date of the version, a Download PDF
|
|
43
|
+
* button with the official document, the text with anchored sections and, from the `lg` breakpoint, a sticky list of
|
|
44
|
+
* the sections. The text is selectable and readable on phones, by search engines and by screen readers; the PDF is
|
|
45
|
+
* generated from the same source.
|
|
46
|
+
*
|
|
47
|
+
* Load the content on the server with `loadPolicy` and render the page inside the layout of the site. The body is
|
|
48
|
+
* HTML from `TuwaIO/workflows`: scripts, styles, frames, forms and event attributes are dropped, and external links
|
|
49
|
+
* open in a new tab. Headings scroll below a sticky header (`scroll-margin-top`), so `/refund#how-to-request-a-refund`
|
|
50
|
+
* opens at its section.
|
|
51
|
+
*
|
|
52
|
+
* @param props - See {@link PolicyDocumentProps}.
|
|
53
|
+
* @returns The policy page.
|
|
54
|
+
*/
|
|
55
|
+
declare function PolicyDocument({ slug, content, source, labels, className, classNames, }: PolicyDocumentProps): React.JSX.Element;
|
|
56
|
+
|
|
57
|
+
export { PolicyDocument, type PolicyDocumentProps };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { PolicySlug, PolicyContent } from '../policies.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Props of {@link PolicyDocument}.
|
|
6
|
+
*/
|
|
7
|
+
interface PolicyDocumentProps {
|
|
8
|
+
/** Slug of the policy; sets the link of the Download PDF button. */
|
|
9
|
+
slug: PolicySlug;
|
|
10
|
+
/** Content of the policy, from `loadPolicy`. */
|
|
11
|
+
content: PolicyContent;
|
|
12
|
+
/** Base URL of the policy files for the PDF link (default: `POLICY_SOURCE`). */
|
|
13
|
+
source?: string;
|
|
14
|
+
/** Texts of the page. */
|
|
15
|
+
labels?: {
|
|
16
|
+
/** Text before the date of the version (default: `Last updated`). */
|
|
17
|
+
updated?: string;
|
|
18
|
+
/** Text of the button that opens the PDF (default: `Download PDF`). */
|
|
19
|
+
download?: string;
|
|
20
|
+
/** Heading and accessible name of the list of sections (default: `On this page`). */
|
|
21
|
+
toc?: string;
|
|
22
|
+
};
|
|
23
|
+
/** Classes of the root element. */
|
|
24
|
+
className?: string;
|
|
25
|
+
/** Classes of the parts of the page. */
|
|
26
|
+
classNames?: {
|
|
27
|
+
/** The header with the title, the date and the button. */
|
|
28
|
+
header?: string;
|
|
29
|
+
/** The title. */
|
|
30
|
+
title?: string;
|
|
31
|
+
/** The date of the version. */
|
|
32
|
+
updated?: string;
|
|
33
|
+
/** The Download PDF button. */
|
|
34
|
+
download?: string;
|
|
35
|
+
/** The `article` with the body. */
|
|
36
|
+
body?: string;
|
|
37
|
+
/** The list of sections (shown from the `lg` breakpoint). */
|
|
38
|
+
toc?: string;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A TUWA policy page (Terms, Privacy, Cookie, Refund, Security): the title, the date of the version, a Download PDF
|
|
43
|
+
* button with the official document, the text with anchored sections and, from the `lg` breakpoint, a sticky list of
|
|
44
|
+
* the sections. The text is selectable and readable on phones, by search engines and by screen readers; the PDF is
|
|
45
|
+
* generated from the same source.
|
|
46
|
+
*
|
|
47
|
+
* Load the content on the server with `loadPolicy` and render the page inside the layout of the site. The body is
|
|
48
|
+
* HTML from `TuwaIO/workflows`: scripts, styles, frames, forms and event attributes are dropped, and external links
|
|
49
|
+
* open in a new tab. Headings scroll below a sticky header (`scroll-margin-top`), so `/refund#how-to-request-a-refund`
|
|
50
|
+
* opens at its section.
|
|
51
|
+
*
|
|
52
|
+
* @param props - See {@link PolicyDocumentProps}.
|
|
53
|
+
* @returns The policy page.
|
|
54
|
+
*/
|
|
55
|
+
declare function PolicyDocument({ slug, content, source, labels, className, classNames, }: PolicyDocumentProps): React.JSX.Element;
|
|
56
|
+
|
|
57
|
+
export { PolicyDocument, type PolicyDocumentProps };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ArrowDownTrayIcon } from "@heroicons/react/24/outline";
|
|
3
|
+
import { cn } from "@tuwaio/nova-core";
|
|
4
|
+
import parse, { Element } from "html-react-parser";
|
|
5
|
+
import { getPolicyFileUrl, POLICY_SOURCE } from "../policies";
|
|
6
|
+
const BLOCKED_ELEMENTS = /* @__PURE__ */ new Set([
|
|
7
|
+
"script",
|
|
8
|
+
"style",
|
|
9
|
+
"iframe",
|
|
10
|
+
"object",
|
|
11
|
+
"embed",
|
|
12
|
+
"form",
|
|
13
|
+
"input",
|
|
14
|
+
"button",
|
|
15
|
+
"textarea",
|
|
16
|
+
"select",
|
|
17
|
+
"link",
|
|
18
|
+
"meta",
|
|
19
|
+
"base"
|
|
20
|
+
]);
|
|
21
|
+
function sanitize(node) {
|
|
22
|
+
if (!(node instanceof Element)) return void 0;
|
|
23
|
+
if (BLOCKED_ELEMENTS.has(node.name)) return /* @__PURE__ */ jsx(Fragment, {});
|
|
24
|
+
for (const name of Object.keys(node.attribs)) {
|
|
25
|
+
if (name.toLowerCase().startsWith("on")) delete node.attribs[name];
|
|
26
|
+
}
|
|
27
|
+
if (node.name === "a") {
|
|
28
|
+
const href = node.attribs.href ?? "";
|
|
29
|
+
if (/^\s*javascript:/i.test(href)) {
|
|
30
|
+
delete node.attribs.href;
|
|
31
|
+
} else if (/^https?:\/\//i.test(href)) {
|
|
32
|
+
node.attribs.target = "_blank";
|
|
33
|
+
node.attribs.rel = "noopener noreferrer";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return void 0;
|
|
37
|
+
}
|
|
38
|
+
function PolicyDocument({
|
|
39
|
+
slug,
|
|
40
|
+
content,
|
|
41
|
+
source = POLICY_SOURCE,
|
|
42
|
+
labels,
|
|
43
|
+
className,
|
|
44
|
+
classNames
|
|
45
|
+
}) {
|
|
46
|
+
const tocLabel = labels?.toc ?? "On this page";
|
|
47
|
+
return /* @__PURE__ */ jsxs(
|
|
48
|
+
"div",
|
|
49
|
+
{
|
|
50
|
+
className: cn(
|
|
51
|
+
"tuwadocs:grid tuwadocs:grid-cols-1 tuwadocs:gap-10 tuwadocs:lg:grid-cols-[minmax(0,1fr)_14rem]",
|
|
52
|
+
className
|
|
53
|
+
),
|
|
54
|
+
children: [
|
|
55
|
+
/* @__PURE__ */ jsxs("div", { className: "tuwadocs:min-w-0", children: [
|
|
56
|
+
/* @__PURE__ */ jsxs(
|
|
57
|
+
"header",
|
|
58
|
+
{
|
|
59
|
+
className: cn(
|
|
60
|
+
"tuwadocs:mb-8 tuwadocs:flex tuwadocs:flex-col tuwadocs:gap-4 tuwadocs:border-b tuwadocs:border-[var(--tuwa-border-primary)] tuwadocs:pb-6",
|
|
61
|
+
classNames?.header
|
|
62
|
+
),
|
|
63
|
+
children: [
|
|
64
|
+
/* @__PURE__ */ jsx(
|
|
65
|
+
"h1",
|
|
66
|
+
{
|
|
67
|
+
className: cn(
|
|
68
|
+
"tuwadocs-policy-title tuwadocs:text-3xl tuwadocs:font-extrabold tuwadocs:tracking-tight tuwadocs:text-[var(--tuwa-text-primary)] tuwadocs:sm:text-4xl",
|
|
69
|
+
classNames?.title
|
|
70
|
+
),
|
|
71
|
+
children: content.title
|
|
72
|
+
}
|
|
73
|
+
),
|
|
74
|
+
/* @__PURE__ */ jsxs("div", { className: "tuwadocs:flex tuwadocs:flex-wrap tuwadocs:items-center tuwadocs:justify-between tuwadocs:gap-3", children: [
|
|
75
|
+
/* @__PURE__ */ jsxs("p", { className: cn("tuwadocs:text-sm tuwadocs:text-[var(--tuwa-text-secondary)]", classNames?.updated), children: [
|
|
76
|
+
labels?.updated ?? "Last updated",
|
|
77
|
+
" ",
|
|
78
|
+
content.updated
|
|
79
|
+
] }),
|
|
80
|
+
/* @__PURE__ */ jsxs(
|
|
81
|
+
"a",
|
|
82
|
+
{
|
|
83
|
+
href: getPolicyFileUrl(slug, "pdf", source),
|
|
84
|
+
target: "_blank",
|
|
85
|
+
rel: "noopener noreferrer",
|
|
86
|
+
className: cn(
|
|
87
|
+
"tuwadocs-policy-title tuwadocs:inline-flex tuwadocs:h-10 tuwadocs:no-underline tuwadocs:items-center tuwadocs:gap-2 tuwadocs:rounded-[var(--tuwa-rounded-corners)] tuwadocs:border tuwadocs:border-[var(--tuwa-border-primary)] tuwadocs:bg-[var(--tuwa-bg-primary)] tuwadocs:px-4 tuwadocs:text-sm tuwadocs:font-bold tuwadocs:text-[var(--tuwa-text-primary)] tuwadocs:transition-colors tuwadocs:hover:bg-[var(--tuwa-bg-muted)]",
|
|
88
|
+
classNames?.download
|
|
89
|
+
),
|
|
90
|
+
children: [
|
|
91
|
+
/* @__PURE__ */ jsx(ArrowDownTrayIcon, { className: "tuwadocs:h-4 tuwadocs:w-4", "aria-hidden": "true" }),
|
|
92
|
+
labels?.download ?? "Download PDF"
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
] })
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
),
|
|
100
|
+
/* @__PURE__ */ jsx("article", { className: cn("tuwadocs-policy", classNames?.body), children: parse(content.html, { replace: sanitize }) })
|
|
101
|
+
] }),
|
|
102
|
+
content.sections.length > 0 && /* @__PURE__ */ jsxs(
|
|
103
|
+
"nav",
|
|
104
|
+
{
|
|
105
|
+
"aria-label": tocLabel,
|
|
106
|
+
className: cn(
|
|
107
|
+
"tuwadocs:hidden tuwadocs:lg:sticky tuwadocs:lg:top-24 tuwadocs:lg:block tuwadocs:lg:self-start",
|
|
108
|
+
classNames?.toc
|
|
109
|
+
),
|
|
110
|
+
children: [
|
|
111
|
+
/* @__PURE__ */ jsx("p", { className: "tuwadocs:mb-3 tuwadocs:text-xs tuwadocs:font-bold tuwadocs:uppercase tuwadocs:tracking-widest tuwadocs:text-[var(--tuwa-text-tertiary)]", children: tocLabel }),
|
|
112
|
+
/* @__PURE__ */ jsx("ol", { className: "tuwadocs:flex tuwadocs:list-none tuwadocs:flex-col tuwadocs:gap-2 tuwadocs:border-l tuwadocs:border-[var(--tuwa-border-primary)] tuwadocs:pl-4", children: content.sections.map((section) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
113
|
+
"a",
|
|
114
|
+
{
|
|
115
|
+
href: `#${section.id}`,
|
|
116
|
+
className: "tuwadocs:text-sm tuwadocs:text-[var(--tuwa-text-secondary)] tuwadocs:no-underline tuwadocs:transition-colors tuwadocs:hover:text-[var(--tuwa-text-primary)]",
|
|
117
|
+
children: section.title
|
|
118
|
+
}
|
|
119
|
+
) }, section.id)) })
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
)
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
export {
|
|
128
|
+
PolicyDocument
|
|
129
|
+
};
|