@orangecheck/ui 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.
- package/LICENSE +21 -0
- package/README.md +41 -0
- package/dist/index.d.mts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +163 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +157 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 OrangeCheck
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# @orangecheck/ui
|
|
2
|
+
|
|
3
|
+
OrangeCheck family-internal UI for the `.ochk.io` sub-sites.
|
|
4
|
+
|
|
5
|
+
This package is **not** for third-party integrators. For embeddable
|
|
6
|
+
OrangeCheck components (badge, gate, signed-challenge button), see
|
|
7
|
+
[`@orangecheck/react`](https://www.npmjs.com/package/@orangecheck/react).
|
|
8
|
+
|
|
9
|
+
## Components
|
|
10
|
+
|
|
11
|
+
### `<EcosystemSwitcher current="…" />`
|
|
12
|
+
|
|
13
|
+
Cross-product dropdown for jumping between every site in the OrangeCheck
|
|
14
|
+
family. Pass the active product slug via `current`.
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { EcosystemSwitcher } from '@orangecheck/ui';
|
|
18
|
+
|
|
19
|
+
<EcosystemSwitcher current="lock" />
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Slugs: `home | docs | attest | lock | vote | stamp | agent | pledge`.
|
|
23
|
+
|
|
24
|
+
## Required host environment
|
|
25
|
+
|
|
26
|
+
Family-internal package — assumes the host is an OC sibling app:
|
|
27
|
+
|
|
28
|
+
- **Next.js 15** (Pages Router or App Router). `<EcosystemSwitcher />` uses
|
|
29
|
+
`next/link` directly.
|
|
30
|
+
- **Tailwind 4** with the OrangeCheck theme tokens defined in the host's
|
|
31
|
+
`globals.css`: `--background`, `--foreground`, `--primary`, `--muted`,
|
|
32
|
+
`--muted-foreground`, plus the custom `font-display` and `label-mono`
|
|
33
|
+
utilities.
|
|
34
|
+
- **lucide-react** for icons.
|
|
35
|
+
|
|
36
|
+
If you're not in an OC family `oc-X-web` app, you probably want
|
|
37
|
+
`@orangecheck/react` instead.
|
|
38
|
+
|
|
39
|
+
## License
|
|
40
|
+
|
|
41
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
type EcosystemSlug = 'home' | 'docs' | 'attest' | 'lock' | 'vote' | 'stamp' | 'agent' | 'pledge';
|
|
4
|
+
interface EcosystemSwitcherProps {
|
|
5
|
+
current: EcosystemSlug;
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
declare function EcosystemSwitcher({ current, className, }: EcosystemSwitcherProps): react_jsx_runtime.JSX.Element;
|
|
9
|
+
|
|
10
|
+
export { type EcosystemSlug, EcosystemSwitcher, type EcosystemSwitcherProps };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
type EcosystemSlug = 'home' | 'docs' | 'attest' | 'lock' | 'vote' | 'stamp' | 'agent' | 'pledge';
|
|
4
|
+
interface EcosystemSwitcherProps {
|
|
5
|
+
current: EcosystemSlug;
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
declare function EcosystemSwitcher({ current, className, }: EcosystemSwitcherProps): react_jsx_runtime.JSX.Element;
|
|
9
|
+
|
|
10
|
+
export { type EcosystemSlug, EcosystemSwitcher, type EcosystemSwitcherProps };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var lucideReact = require('lucide-react');
|
|
4
|
+
var Link = require('next/link');
|
|
5
|
+
var react = require('react');
|
|
6
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
+
|
|
8
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
|
+
|
|
10
|
+
var Link__default = /*#__PURE__*/_interopDefault(Link);
|
|
11
|
+
|
|
12
|
+
// src/ecosystem-switcher.tsx
|
|
13
|
+
var ENTRIES = [
|
|
14
|
+
{
|
|
15
|
+
slug: "home",
|
|
16
|
+
href: "https://ochk.io",
|
|
17
|
+
label: "orangecheck",
|
|
18
|
+
sub: "umbrella",
|
|
19
|
+
docsHref: "https://docs.ochk.io"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
slug: "docs",
|
|
23
|
+
href: "https://docs.ochk.io",
|
|
24
|
+
label: "oc\xB7docs",
|
|
25
|
+
sub: "unified docs",
|
|
26
|
+
docsHref: "https://docs.ochk.io"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
slug: "attest",
|
|
30
|
+
href: "https://attest.ochk.io",
|
|
31
|
+
label: "oc\xB7attest",
|
|
32
|
+
sub: "am \u2014 sybil resistance",
|
|
33
|
+
docsHref: "https://docs.ochk.io/attest"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
slug: "lock",
|
|
37
|
+
href: "https://lock.ochk.io",
|
|
38
|
+
label: "oc\xB7lock",
|
|
39
|
+
sub: "whisper \u2014 encryption",
|
|
40
|
+
docsHref: "https://docs.ochk.io/lock"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
slug: "vote",
|
|
44
|
+
href: "https://vote.ochk.io",
|
|
45
|
+
label: "oc\xB7vote",
|
|
46
|
+
sub: "decide \u2014 polls",
|
|
47
|
+
docsHref: "https://docs.ochk.io/vote"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
slug: "stamp",
|
|
51
|
+
href: "https://stamp.ochk.io",
|
|
52
|
+
label: "oc\xB7stamp",
|
|
53
|
+
sub: "declare \u2014 block-anchored",
|
|
54
|
+
docsHref: "https://docs.ochk.io/stamp"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
slug: "agent",
|
|
58
|
+
href: "https://agent.ochk.io",
|
|
59
|
+
label: "oc\xB7agent",
|
|
60
|
+
sub: "delegate \u2014 scoped auth",
|
|
61
|
+
docsHref: "https://docs.ochk.io/agent"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
slug: "pledge",
|
|
65
|
+
href: "https://pledge.ochk.io",
|
|
66
|
+
label: "oc\xB7pledge",
|
|
67
|
+
sub: "swear \u2014 bonded commitment",
|
|
68
|
+
docsHref: "https://docs.ochk.io/pledge"
|
|
69
|
+
}
|
|
70
|
+
];
|
|
71
|
+
function EcosystemSwitcher({
|
|
72
|
+
current,
|
|
73
|
+
className
|
|
74
|
+
}) {
|
|
75
|
+
const [open, setOpen] = react.useState(false);
|
|
76
|
+
const containerRef = react.useRef(null);
|
|
77
|
+
react.useEffect(() => {
|
|
78
|
+
if (!open) return;
|
|
79
|
+
function onDoc(e) {
|
|
80
|
+
if (!containerRef.current) return;
|
|
81
|
+
if (!containerRef.current.contains(e.target)) setOpen(false);
|
|
82
|
+
}
|
|
83
|
+
function onKey(e) {
|
|
84
|
+
if (e.key === "Escape") setOpen(false);
|
|
85
|
+
}
|
|
86
|
+
document.addEventListener("mousedown", onDoc);
|
|
87
|
+
document.addEventListener("keydown", onKey);
|
|
88
|
+
return () => {
|
|
89
|
+
document.removeEventListener("mousedown", onDoc);
|
|
90
|
+
document.removeEventListener("keydown", onKey);
|
|
91
|
+
};
|
|
92
|
+
}, [open]);
|
|
93
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative " + (className ?? ""), children: [
|
|
94
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
95
|
+
"button",
|
|
96
|
+
{
|
|
97
|
+
type: "button",
|
|
98
|
+
"aria-haspopup": "menu",
|
|
99
|
+
"aria-expanded": open,
|
|
100
|
+
"aria-label": "Switch OrangeCheck product",
|
|
101
|
+
title: "Switch product",
|
|
102
|
+
onClick: () => setOpen((v) => !v),
|
|
103
|
+
className: "inline-flex items-center gap-1 px-2 py-1 transition-colors " + (open ? "text-foreground" : "text-muted-foreground hover:text-foreground"),
|
|
104
|
+
children: [
|
|
105
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Boxes, { className: "h-3.5 w-3.5" }),
|
|
106
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
107
|
+
lucideReact.ChevronDown,
|
|
108
|
+
{
|
|
109
|
+
className: "h-3 w-3 transition-transform " + (open ? "rotate-180" : "")
|
|
110
|
+
}
|
|
111
|
+
)
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
),
|
|
115
|
+
open && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
116
|
+
"div",
|
|
117
|
+
{
|
|
118
|
+
role: "menu",
|
|
119
|
+
"aria-label": "Switch OrangeCheck product",
|
|
120
|
+
className: "bg-background absolute right-0 top-full z-[60] mt-2 w-72 border shadow-lg",
|
|
121
|
+
children: [
|
|
122
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "label-mono text-primary border-b px-4 py-2", children: "\xA7 the family" }),
|
|
123
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "py-1", children: ENTRIES.map((e) => {
|
|
124
|
+
const isActive = e.slug === current;
|
|
125
|
+
return /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
126
|
+
Link__default.default,
|
|
127
|
+
{
|
|
128
|
+
href: e.href,
|
|
129
|
+
onClick: () => setOpen(false),
|
|
130
|
+
"aria-current": isActive ? "page" : void 0,
|
|
131
|
+
className: "group flex items-baseline gap-3 px-4 py-2 transition-colors " + (isActive ? "bg-primary/5" : "hover:bg-muted"),
|
|
132
|
+
children: [
|
|
133
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
134
|
+
"span",
|
|
135
|
+
{
|
|
136
|
+
className: "font-display flex-1 text-[12px] font-semibold tracking-tight " + (isActive ? "text-primary" : "text-foreground group-hover:text-primary transition-colors"),
|
|
137
|
+
children: e.label
|
|
138
|
+
}
|
|
139
|
+
),
|
|
140
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground font-mono text-[10px] tracking-wider uppercase", children: e.sub }),
|
|
141
|
+
isActive && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "text-primary h-3 w-3" })
|
|
142
|
+
]
|
|
143
|
+
}
|
|
144
|
+
) }, e.slug);
|
|
145
|
+
}) }),
|
|
146
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t px-4 py-2 font-mono text-[10px] tracking-widest uppercase", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
147
|
+
Link__default.default,
|
|
148
|
+
{
|
|
149
|
+
href: "https://docs.ochk.io",
|
|
150
|
+
onClick: () => setOpen(false),
|
|
151
|
+
className: "text-muted-foreground hover:text-foreground inline-block transition-colors",
|
|
152
|
+
children: "docs.ochk.io \u2192"
|
|
153
|
+
}
|
|
154
|
+
) })
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
)
|
|
158
|
+
] });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
exports.EcosystemSwitcher = EcosystemSwitcher;
|
|
162
|
+
//# sourceMappingURL=index.js.map
|
|
163
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ecosystem-switcher.tsx"],"names":["useState","useRef","useEffect","jsxs","jsx","Boxes","ChevronDown","Link","Check"],"mappings":"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","file":"index.js","sourcesContent":["import { Boxes, Check, ChevronDown } from 'lucide-react';\nimport Link from 'next/link';\nimport { useEffect, useRef, useState } from 'react';\n\n/**\n * EcosystemSwitcher — cross-product dropdown for jumping between every\n * site in the OrangeCheck family. Drop one into every site's LayoutHeader\n * and mark the active site via the `current` prop.\n *\n * <EcosystemSwitcher current=\"lock\" />\n *\n * The component is dependency-self-contained: no Radix, no Headless UI,\n * just outside-click + Escape handling so it works identically in every\n * site without forcing a peer-dep upgrade. Every link stays in-tab — the\n * family is one app from the user's POV.\n */\n\nexport type EcosystemSlug =\n | 'home'\n | 'docs'\n | 'attest'\n | 'lock'\n | 'vote'\n | 'stamp'\n | 'agent'\n | 'pledge';\n\ninterface SwitcherEntry {\n slug: EcosystemSlug;\n href: string;\n label: string;\n sub: string;\n docsHref: string;\n}\n\nconst ENTRIES: SwitcherEntry[] = [\n {\n slug: 'home',\n href: 'https://ochk.io',\n label: 'orangecheck',\n sub: 'umbrella',\n docsHref: 'https://docs.ochk.io',\n },\n {\n slug: 'docs',\n href: 'https://docs.ochk.io',\n label: 'oc·docs',\n sub: 'unified docs',\n docsHref: 'https://docs.ochk.io',\n },\n {\n slug: 'attest',\n href: 'https://attest.ochk.io',\n label: 'oc·attest',\n sub: 'am — sybil resistance',\n docsHref: 'https://docs.ochk.io/attest',\n },\n {\n slug: 'lock',\n href: 'https://lock.ochk.io',\n label: 'oc·lock',\n sub: 'whisper — encryption',\n docsHref: 'https://docs.ochk.io/lock',\n },\n {\n slug: 'vote',\n href: 'https://vote.ochk.io',\n label: 'oc·vote',\n sub: 'decide — polls',\n docsHref: 'https://docs.ochk.io/vote',\n },\n {\n slug: 'stamp',\n href: 'https://stamp.ochk.io',\n label: 'oc·stamp',\n sub: 'declare — block-anchored',\n docsHref: 'https://docs.ochk.io/stamp',\n },\n {\n slug: 'agent',\n href: 'https://agent.ochk.io',\n label: 'oc·agent',\n sub: 'delegate — scoped auth',\n docsHref: 'https://docs.ochk.io/agent',\n },\n {\n slug: 'pledge',\n href: 'https://pledge.ochk.io',\n label: 'oc·pledge',\n sub: 'swear — bonded commitment',\n docsHref: 'https://docs.ochk.io/pledge',\n },\n];\n\nexport interface EcosystemSwitcherProps {\n current: EcosystemSlug;\n className?: string;\n}\n\nexport function EcosystemSwitcher({\n current,\n className,\n}: EcosystemSwitcherProps) {\n const [open, setOpen] = useState(false);\n const containerRef = useRef<HTMLDivElement | null>(null);\n\n // Outside-click + Escape close.\n useEffect(() => {\n if (!open) return;\n function onDoc(e: MouseEvent) {\n if (!containerRef.current) return;\n if (!containerRef.current.contains(e.target as Node)) setOpen(false);\n }\n function onKey(e: KeyboardEvent) {\n if (e.key === 'Escape') setOpen(false);\n }\n document.addEventListener('mousedown', onDoc);\n document.addEventListener('keydown', onKey);\n return () => {\n document.removeEventListener('mousedown', onDoc);\n document.removeEventListener('keydown', onKey);\n };\n }, [open]);\n\n return (\n <div ref={containerRef} className={'relative ' + (className ?? '')}>\n <button\n type=\"button\"\n aria-haspopup=\"menu\"\n aria-expanded={open}\n aria-label=\"Switch OrangeCheck product\"\n title=\"Switch product\"\n onClick={() => setOpen((v) => !v)}\n className={\n 'inline-flex items-center gap-1 px-2 py-1 transition-colors ' +\n (open\n ? 'text-foreground'\n : 'text-muted-foreground hover:text-foreground')\n }\n >\n <Boxes className=\"h-3.5 w-3.5\" />\n <ChevronDown\n className={\n 'h-3 w-3 transition-transform ' + (open ? 'rotate-180' : '')\n }\n />\n </button>\n\n {open && (\n <div\n role=\"menu\"\n aria-label=\"Switch OrangeCheck product\"\n className=\"bg-background absolute right-0 top-full z-[60] mt-2 w-72 border shadow-lg\"\n >\n <div className=\"label-mono text-primary border-b px-4 py-2\">\n § the family\n </div>\n <ul className=\"py-1\">\n {ENTRIES.map((e) => {\n const isActive = e.slug === current;\n return (\n <li key={e.slug}>\n <Link\n href={e.href}\n onClick={() => setOpen(false)}\n aria-current={isActive ? 'page' : undefined}\n className={\n 'group flex items-baseline gap-3 px-4 py-2 transition-colors ' +\n (isActive\n ? 'bg-primary/5'\n : 'hover:bg-muted')\n }\n >\n <span\n className={\n 'font-display flex-1 text-[12px] font-semibold tracking-tight ' +\n (isActive\n ? 'text-primary'\n : 'text-foreground group-hover:text-primary transition-colors')\n }\n >\n {e.label}\n </span>\n <span className=\"text-muted-foreground font-mono text-[10px] tracking-wider uppercase\">\n {e.sub}\n </span>\n {isActive && (\n <Check className=\"text-primary h-3 w-3\" />\n )}\n </Link>\n </li>\n );\n })}\n </ul>\n <div className=\"border-t px-4 py-2 font-mono text-[10px] tracking-widest uppercase\">\n <Link\n href=\"https://docs.ochk.io\"\n onClick={() => setOpen(false)}\n className=\"text-muted-foreground hover:text-foreground inline-block transition-colors\"\n >\n docs.ochk.io →\n </Link>\n </div>\n </div>\n )}\n </div>\n );\n}\n"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Boxes, ChevronDown, Check } from 'lucide-react';
|
|
2
|
+
import Link from 'next/link';
|
|
3
|
+
import { useState, useRef, useEffect } from 'react';
|
|
4
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
// src/ecosystem-switcher.tsx
|
|
7
|
+
var ENTRIES = [
|
|
8
|
+
{
|
|
9
|
+
slug: "home",
|
|
10
|
+
href: "https://ochk.io",
|
|
11
|
+
label: "orangecheck",
|
|
12
|
+
sub: "umbrella",
|
|
13
|
+
docsHref: "https://docs.ochk.io"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
slug: "docs",
|
|
17
|
+
href: "https://docs.ochk.io",
|
|
18
|
+
label: "oc\xB7docs",
|
|
19
|
+
sub: "unified docs",
|
|
20
|
+
docsHref: "https://docs.ochk.io"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
slug: "attest",
|
|
24
|
+
href: "https://attest.ochk.io",
|
|
25
|
+
label: "oc\xB7attest",
|
|
26
|
+
sub: "am \u2014 sybil resistance",
|
|
27
|
+
docsHref: "https://docs.ochk.io/attest"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
slug: "lock",
|
|
31
|
+
href: "https://lock.ochk.io",
|
|
32
|
+
label: "oc\xB7lock",
|
|
33
|
+
sub: "whisper \u2014 encryption",
|
|
34
|
+
docsHref: "https://docs.ochk.io/lock"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
slug: "vote",
|
|
38
|
+
href: "https://vote.ochk.io",
|
|
39
|
+
label: "oc\xB7vote",
|
|
40
|
+
sub: "decide \u2014 polls",
|
|
41
|
+
docsHref: "https://docs.ochk.io/vote"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
slug: "stamp",
|
|
45
|
+
href: "https://stamp.ochk.io",
|
|
46
|
+
label: "oc\xB7stamp",
|
|
47
|
+
sub: "declare \u2014 block-anchored",
|
|
48
|
+
docsHref: "https://docs.ochk.io/stamp"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
slug: "agent",
|
|
52
|
+
href: "https://agent.ochk.io",
|
|
53
|
+
label: "oc\xB7agent",
|
|
54
|
+
sub: "delegate \u2014 scoped auth",
|
|
55
|
+
docsHref: "https://docs.ochk.io/agent"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
slug: "pledge",
|
|
59
|
+
href: "https://pledge.ochk.io",
|
|
60
|
+
label: "oc\xB7pledge",
|
|
61
|
+
sub: "swear \u2014 bonded commitment",
|
|
62
|
+
docsHref: "https://docs.ochk.io/pledge"
|
|
63
|
+
}
|
|
64
|
+
];
|
|
65
|
+
function EcosystemSwitcher({
|
|
66
|
+
current,
|
|
67
|
+
className
|
|
68
|
+
}) {
|
|
69
|
+
const [open, setOpen] = useState(false);
|
|
70
|
+
const containerRef = useRef(null);
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (!open) return;
|
|
73
|
+
function onDoc(e) {
|
|
74
|
+
if (!containerRef.current) return;
|
|
75
|
+
if (!containerRef.current.contains(e.target)) setOpen(false);
|
|
76
|
+
}
|
|
77
|
+
function onKey(e) {
|
|
78
|
+
if (e.key === "Escape") setOpen(false);
|
|
79
|
+
}
|
|
80
|
+
document.addEventListener("mousedown", onDoc);
|
|
81
|
+
document.addEventListener("keydown", onKey);
|
|
82
|
+
return () => {
|
|
83
|
+
document.removeEventListener("mousedown", onDoc);
|
|
84
|
+
document.removeEventListener("keydown", onKey);
|
|
85
|
+
};
|
|
86
|
+
}, [open]);
|
|
87
|
+
return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "relative " + (className ?? ""), children: [
|
|
88
|
+
/* @__PURE__ */ jsxs(
|
|
89
|
+
"button",
|
|
90
|
+
{
|
|
91
|
+
type: "button",
|
|
92
|
+
"aria-haspopup": "menu",
|
|
93
|
+
"aria-expanded": open,
|
|
94
|
+
"aria-label": "Switch OrangeCheck product",
|
|
95
|
+
title: "Switch product",
|
|
96
|
+
onClick: () => setOpen((v) => !v),
|
|
97
|
+
className: "inline-flex items-center gap-1 px-2 py-1 transition-colors " + (open ? "text-foreground" : "text-muted-foreground hover:text-foreground"),
|
|
98
|
+
children: [
|
|
99
|
+
/* @__PURE__ */ jsx(Boxes, { className: "h-3.5 w-3.5" }),
|
|
100
|
+
/* @__PURE__ */ jsx(
|
|
101
|
+
ChevronDown,
|
|
102
|
+
{
|
|
103
|
+
className: "h-3 w-3 transition-transform " + (open ? "rotate-180" : "")
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
),
|
|
109
|
+
open && /* @__PURE__ */ jsxs(
|
|
110
|
+
"div",
|
|
111
|
+
{
|
|
112
|
+
role: "menu",
|
|
113
|
+
"aria-label": "Switch OrangeCheck product",
|
|
114
|
+
className: "bg-background absolute right-0 top-full z-[60] mt-2 w-72 border shadow-lg",
|
|
115
|
+
children: [
|
|
116
|
+
/* @__PURE__ */ jsx("div", { className: "label-mono text-primary border-b px-4 py-2", children: "\xA7 the family" }),
|
|
117
|
+
/* @__PURE__ */ jsx("ul", { className: "py-1", children: ENTRIES.map((e) => {
|
|
118
|
+
const isActive = e.slug === current;
|
|
119
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
120
|
+
Link,
|
|
121
|
+
{
|
|
122
|
+
href: e.href,
|
|
123
|
+
onClick: () => setOpen(false),
|
|
124
|
+
"aria-current": isActive ? "page" : void 0,
|
|
125
|
+
className: "group flex items-baseline gap-3 px-4 py-2 transition-colors " + (isActive ? "bg-primary/5" : "hover:bg-muted"),
|
|
126
|
+
children: [
|
|
127
|
+
/* @__PURE__ */ jsx(
|
|
128
|
+
"span",
|
|
129
|
+
{
|
|
130
|
+
className: "font-display flex-1 text-[12px] font-semibold tracking-tight " + (isActive ? "text-primary" : "text-foreground group-hover:text-primary transition-colors"),
|
|
131
|
+
children: e.label
|
|
132
|
+
}
|
|
133
|
+
),
|
|
134
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground font-mono text-[10px] tracking-wider uppercase", children: e.sub }),
|
|
135
|
+
isActive && /* @__PURE__ */ jsx(Check, { className: "text-primary h-3 w-3" })
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
) }, e.slug);
|
|
139
|
+
}) }),
|
|
140
|
+
/* @__PURE__ */ jsx("div", { className: "border-t px-4 py-2 font-mono text-[10px] tracking-widest uppercase", children: /* @__PURE__ */ jsx(
|
|
141
|
+
Link,
|
|
142
|
+
{
|
|
143
|
+
href: "https://docs.ochk.io",
|
|
144
|
+
onClick: () => setOpen(false),
|
|
145
|
+
className: "text-muted-foreground hover:text-foreground inline-block transition-colors",
|
|
146
|
+
children: "docs.ochk.io \u2192"
|
|
147
|
+
}
|
|
148
|
+
) })
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
] });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export { EcosystemSwitcher };
|
|
156
|
+
//# sourceMappingURL=index.mjs.map
|
|
157
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ecosystem-switcher.tsx"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import { Boxes, Check, ChevronDown } from 'lucide-react';\nimport Link from 'next/link';\nimport { useEffect, useRef, useState } from 'react';\n\n/**\n * EcosystemSwitcher — cross-product dropdown for jumping between every\n * site in the OrangeCheck family. Drop one into every site's LayoutHeader\n * and mark the active site via the `current` prop.\n *\n * <EcosystemSwitcher current=\"lock\" />\n *\n * The component is dependency-self-contained: no Radix, no Headless UI,\n * just outside-click + Escape handling so it works identically in every\n * site without forcing a peer-dep upgrade. Every link stays in-tab — the\n * family is one app from the user's POV.\n */\n\nexport type EcosystemSlug =\n | 'home'\n | 'docs'\n | 'attest'\n | 'lock'\n | 'vote'\n | 'stamp'\n | 'agent'\n | 'pledge';\n\ninterface SwitcherEntry {\n slug: EcosystemSlug;\n href: string;\n label: string;\n sub: string;\n docsHref: string;\n}\n\nconst ENTRIES: SwitcherEntry[] = [\n {\n slug: 'home',\n href: 'https://ochk.io',\n label: 'orangecheck',\n sub: 'umbrella',\n docsHref: 'https://docs.ochk.io',\n },\n {\n slug: 'docs',\n href: 'https://docs.ochk.io',\n label: 'oc·docs',\n sub: 'unified docs',\n docsHref: 'https://docs.ochk.io',\n },\n {\n slug: 'attest',\n href: 'https://attest.ochk.io',\n label: 'oc·attest',\n sub: 'am — sybil resistance',\n docsHref: 'https://docs.ochk.io/attest',\n },\n {\n slug: 'lock',\n href: 'https://lock.ochk.io',\n label: 'oc·lock',\n sub: 'whisper — encryption',\n docsHref: 'https://docs.ochk.io/lock',\n },\n {\n slug: 'vote',\n href: 'https://vote.ochk.io',\n label: 'oc·vote',\n sub: 'decide — polls',\n docsHref: 'https://docs.ochk.io/vote',\n },\n {\n slug: 'stamp',\n href: 'https://stamp.ochk.io',\n label: 'oc·stamp',\n sub: 'declare — block-anchored',\n docsHref: 'https://docs.ochk.io/stamp',\n },\n {\n slug: 'agent',\n href: 'https://agent.ochk.io',\n label: 'oc·agent',\n sub: 'delegate — scoped auth',\n docsHref: 'https://docs.ochk.io/agent',\n },\n {\n slug: 'pledge',\n href: 'https://pledge.ochk.io',\n label: 'oc·pledge',\n sub: 'swear — bonded commitment',\n docsHref: 'https://docs.ochk.io/pledge',\n },\n];\n\nexport interface EcosystemSwitcherProps {\n current: EcosystemSlug;\n className?: string;\n}\n\nexport function EcosystemSwitcher({\n current,\n className,\n}: EcosystemSwitcherProps) {\n const [open, setOpen] = useState(false);\n const containerRef = useRef<HTMLDivElement | null>(null);\n\n // Outside-click + Escape close.\n useEffect(() => {\n if (!open) return;\n function onDoc(e: MouseEvent) {\n if (!containerRef.current) return;\n if (!containerRef.current.contains(e.target as Node)) setOpen(false);\n }\n function onKey(e: KeyboardEvent) {\n if (e.key === 'Escape') setOpen(false);\n }\n document.addEventListener('mousedown', onDoc);\n document.addEventListener('keydown', onKey);\n return () => {\n document.removeEventListener('mousedown', onDoc);\n document.removeEventListener('keydown', onKey);\n };\n }, [open]);\n\n return (\n <div ref={containerRef} className={'relative ' + (className ?? '')}>\n <button\n type=\"button\"\n aria-haspopup=\"menu\"\n aria-expanded={open}\n aria-label=\"Switch OrangeCheck product\"\n title=\"Switch product\"\n onClick={() => setOpen((v) => !v)}\n className={\n 'inline-flex items-center gap-1 px-2 py-1 transition-colors ' +\n (open\n ? 'text-foreground'\n : 'text-muted-foreground hover:text-foreground')\n }\n >\n <Boxes className=\"h-3.5 w-3.5\" />\n <ChevronDown\n className={\n 'h-3 w-3 transition-transform ' + (open ? 'rotate-180' : '')\n }\n />\n </button>\n\n {open && (\n <div\n role=\"menu\"\n aria-label=\"Switch OrangeCheck product\"\n className=\"bg-background absolute right-0 top-full z-[60] mt-2 w-72 border shadow-lg\"\n >\n <div className=\"label-mono text-primary border-b px-4 py-2\">\n § the family\n </div>\n <ul className=\"py-1\">\n {ENTRIES.map((e) => {\n const isActive = e.slug === current;\n return (\n <li key={e.slug}>\n <Link\n href={e.href}\n onClick={() => setOpen(false)}\n aria-current={isActive ? 'page' : undefined}\n className={\n 'group flex items-baseline gap-3 px-4 py-2 transition-colors ' +\n (isActive\n ? 'bg-primary/5'\n : 'hover:bg-muted')\n }\n >\n <span\n className={\n 'font-display flex-1 text-[12px] font-semibold tracking-tight ' +\n (isActive\n ? 'text-primary'\n : 'text-foreground group-hover:text-primary transition-colors')\n }\n >\n {e.label}\n </span>\n <span className=\"text-muted-foreground font-mono text-[10px] tracking-wider uppercase\">\n {e.sub}\n </span>\n {isActive && (\n <Check className=\"text-primary h-3 w-3\" />\n )}\n </Link>\n </li>\n );\n })}\n </ul>\n <div className=\"border-t px-4 py-2 font-mono text-[10px] tracking-widest uppercase\">\n <Link\n href=\"https://docs.ochk.io\"\n onClick={() => setOpen(false)}\n className=\"text-muted-foreground hover:text-foreground inline-block transition-colors\"\n >\n docs.ochk.io →\n </Link>\n </div>\n </div>\n )}\n </div>\n );\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@orangecheck/ui",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OrangeCheck family-internal UI for the .ochk.io sub-sites. Tailwind 4 + Next.js Pages Router. Not for third-party integration.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"orangecheck",
|
|
7
|
+
"ui",
|
|
8
|
+
"react",
|
|
9
|
+
"internal"
|
|
10
|
+
],
|
|
11
|
+
"author": "OrangeCheck",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"homepage": "https://ochk.io",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/orangecheck/oc-packages.git",
|
|
17
|
+
"directory": "ui"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/orangecheck/oc-packages/issues"
|
|
21
|
+
},
|
|
22
|
+
"main": "./dist/index.js",
|
|
23
|
+
"module": "./dist/index.mjs",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.mjs",
|
|
29
|
+
"require": "./dist/index.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"README.md",
|
|
35
|
+
"LICENSE"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsup",
|
|
39
|
+
"dev": "tsup --watch",
|
|
40
|
+
"type-check": "tsc --noEmit",
|
|
41
|
+
"clean": "rm -rf dist",
|
|
42
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"lucide-react": ">=0.400.0",
|
|
46
|
+
"next": "^15.0.0",
|
|
47
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
48
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/react": "^19.0.0",
|
|
52
|
+
"@types/react-dom": "^19.0.0",
|
|
53
|
+
"lucide-react": "^0.544.0",
|
|
54
|
+
"next": "15.5.15",
|
|
55
|
+
"react": "19.1.0",
|
|
56
|
+
"react-dom": "19.1.0",
|
|
57
|
+
"tsup": "^8.3.5",
|
|
58
|
+
"typescript": "^5.6.3"
|
|
59
|
+
},
|
|
60
|
+
"publishConfig": {
|
|
61
|
+
"access": "public"
|
|
62
|
+
}
|
|
63
|
+
}
|