@xforge-dev/forge-kit 0.14.0 → 0.16.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/dist/account-url.d.ts +1 -1
- package/dist/client.d.ts +2 -0
- package/dist/client.js +2 -0
- package/dist/forge-footer.d.ts +15 -0
- package/dist/forge-footer.js +9 -0
- package/dist/ign-input.d.ts +30 -0
- package/dist/ign-input.js +49 -0
- package/package.json +3 -1
package/dist/account-url.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const ACCOUNT_ORIGIN: string;
|
|
2
|
-
export type AccountPath = '/' | '/login' | '/register' | '/logout' | '/reset-password';
|
|
2
|
+
export type AccountPath = '/' | '/login' | '/register' | '/logout' | '/reset-password' | '/privacy' | '/terms';
|
|
3
3
|
export declare function accountUrl(path: AccountPath, opts?: {
|
|
4
4
|
next?: string;
|
|
5
5
|
}, origin?: string): string;
|
package/dist/client.d.ts
CHANGED
|
@@ -10,3 +10,5 @@ export { ForgeHeader, ForgeAvatar, ForgePanelSection, ForgePanelItem, ForgeSwitc
|
|
|
10
10
|
export * from './theme.js';
|
|
11
11
|
export { ForgeLogo, type ForgeLogoProps } from './forge-logo.js';
|
|
12
12
|
export { ForgeSupportButton, BUY_ME_A_COFFEE_URL, BUY_ME_A_COFFEE_BUTTON, FORGE_SPONSOR_TIERS, ForgeSupporterBadge, type ForgeSupporterTier, type ForgeSupportButtonProps, } from './support-button.js';
|
|
13
|
+
export { ForgeIgnInput, FORGE_ACCENTED_LETTERS, FORGE_NO_AUTOFILL, type ForgeIgnInputProps, } from './ign-input.js';
|
|
14
|
+
export { ForgeFooter, type ForgeFooterProps } from './forge-footer.js';
|
package/dist/client.js
CHANGED
|
@@ -11,3 +11,5 @@ export { ForgeHeader, ForgeAvatar, ForgePanelSection, ForgePanelItem, ForgeSwitc
|
|
|
11
11
|
export * from './theme.js';
|
|
12
12
|
export { ForgeLogo } from './forge-logo.js';
|
|
13
13
|
export { ForgeSupportButton, BUY_ME_A_COFFEE_URL, BUY_ME_A_COFFEE_BUTTON, FORGE_SPONSOR_TIERS, ForgeSupporterBadge, } from './support-button.js';
|
|
14
|
+
export { ForgeIgnInput, FORGE_ACCENTED_LETTERS, FORGE_NO_AUTOFILL, } from './ign-input.js';
|
|
15
|
+
export { ForgeFooter } from './forge-footer.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export type ForgeFooterProps = {
|
|
3
|
+
/** The app's release version, e.g. `process.env.APP_VERSION`. */
|
|
4
|
+
version?: string;
|
|
5
|
+
/** Short commit SHA of the build. */
|
|
6
|
+
commit?: string;
|
|
7
|
+
/** The app's docs page; omitted, no Docs link. */
|
|
8
|
+
docsHref?: string;
|
|
9
|
+
/** Extra links, after Terms. */
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
/** Width of the footer's inner row; defaults to `max-w-6xl`. */
|
|
12
|
+
containerClassName?: string;
|
|
13
|
+
};
|
|
14
|
+
/** The family footer: copyright, docs, the shared privacy policy and terms, and the build. */
|
|
15
|
+
export declare function ForgeFooter({ version, commit, docsHref, children, containerClassName }: ForgeFooterProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import Link from 'next/link.js';
|
|
4
|
+
import { accountUrl } from './account-url.js';
|
|
5
|
+
const linkClass = 'hover:text-foreground underline-offset-4 hover:underline';
|
|
6
|
+
/** The family footer: copyright, docs, the shared privacy policy and terms, and the build. */
|
|
7
|
+
export function ForgeFooter({ version, commit, docsHref, children, containerClassName = 'max-w-6xl' }) {
|
|
8
|
+
return (_jsx("footer", { className: "w-full border-t border-border/40 bg-background/95", children: _jsxs("div", { className: `mx-auto flex min-h-14 flex-wrap items-center justify-center gap-x-4 gap-y-1 px-4 py-3 text-sm text-muted-foreground ${containerClassName}`, children: [_jsxs("p", { children: ["\u00A9 ", new Date().getFullYear(), " xForge"] }), docsHref && _jsx(Link, { href: docsHref, className: linkClass, children: "Docs" }), _jsx("a", { href: accountUrl('/privacy'), className: linkClass, children: "Privacy" }), _jsx("a", { href: accountUrl('/terms'), className: linkClass, children: "Terms" }), children, (version || commit) && (_jsxs("p", { children: [version && `v${version}`, commit && _jsxs("span", { className: "font-mono text-xs", children: [" (", commit, ")"] })] }))] }) }));
|
|
9
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type ComponentProps } from 'react';
|
|
2
|
+
/** Accented letters MapleStory names allow, for names like Sïnfùl that are hard to type. */
|
|
3
|
+
export declare const FORGE_ACCENTED_LETTERS: string[];
|
|
4
|
+
/**
|
|
5
|
+
* Spread onto text fields that aren't credentials. Browsers ignore autocomplete="off" for anything that looks like a
|
|
6
|
+
* login, so the common password managers get their own opt-outs: 1Password, LastPass, Bitwarden and Dashlane.
|
|
7
|
+
*/
|
|
8
|
+
export declare const FORGE_NO_AUTOFILL: {
|
|
9
|
+
readonly autoComplete: "off";
|
|
10
|
+
readonly 'data-1p-ignore': "true";
|
|
11
|
+
readonly 'data-lpignore': "true";
|
|
12
|
+
readonly 'data-bwignore': "true";
|
|
13
|
+
readonly 'data-form-type': "other";
|
|
14
|
+
};
|
|
15
|
+
export type ForgeIgnInputProps = Omit<ComponentProps<'input'>, 'value' | 'onChange'> & {
|
|
16
|
+
value: string;
|
|
17
|
+
onValueChange: (value: string) => void;
|
|
18
|
+
/** Names used before on this device, shown as chips under the field. Picking one hands it to `onPick`. */
|
|
19
|
+
history?: string[];
|
|
20
|
+
onPick?: (name: string) => void;
|
|
21
|
+
/** Shows a remove button on each history chip. */
|
|
22
|
+
onForget?: (name: string) => void;
|
|
23
|
+
/** Classes for the outer wrapper (the field plus history chips); `className` goes on the input. */
|
|
24
|
+
wrapperClassName?: string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* A character name field with a picker for the accented letters MapleStory names allow, and optionally the names
|
|
28
|
+
* used before, so a name never has to be typed twice. Styled like the family's 36px inputs.
|
|
29
|
+
*/
|
|
30
|
+
export declare function ForgeIgnInput({ value, onValueChange, history, onPick, onForget, className, wrapperClassName, onSelect, onBlur, ...inputProps }: ForgeIgnInputProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as Popover from '@radix-ui/react-popover';
|
|
4
|
+
import { useRef, useState } from 'react';
|
|
5
|
+
import { ArrowBigUp, X } from 'lucide-react';
|
|
6
|
+
/** Accented letters MapleStory names allow, for names like Sïnfùl that are hard to type. */
|
|
7
|
+
export const FORGE_ACCENTED_LETTERS = 'àáâãäåæçèéêëìíîïðñòóôõöøœùúûüýÿþß'.split('');
|
|
8
|
+
/**
|
|
9
|
+
* Spread onto text fields that aren't credentials. Browsers ignore autocomplete="off" for anything that looks like a
|
|
10
|
+
* login, so the common password managers get their own opt-outs: 1Password, LastPass, Bitwarden and Dashlane.
|
|
11
|
+
*/
|
|
12
|
+
export const FORGE_NO_AUTOFILL = {
|
|
13
|
+
autoComplete: 'off',
|
|
14
|
+
'data-1p-ignore': 'true',
|
|
15
|
+
'data-lpignore': 'true',
|
|
16
|
+
'data-bwignore': 'true',
|
|
17
|
+
'data-form-type': 'other',
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* A character name field with a picker for the accented letters MapleStory names allow, and optionally the names
|
|
21
|
+
* used before, so a name never has to be typed twice. Styled like the family's 36px inputs.
|
|
22
|
+
*/
|
|
23
|
+
export function ForgeIgnInput({ value, onValueChange, history = [], onPick, onForget, className = '', wrapperClassName = '', onSelect, onBlur, ...inputProps }) {
|
|
24
|
+
const inputRef = useRef(null);
|
|
25
|
+
// Where the caret was before the picker took focus, so a letter lands where it was meant to.
|
|
26
|
+
const selectionRef = useRef(null);
|
|
27
|
+
const [upper, setUpper] = useState(false);
|
|
28
|
+
const remember = () => {
|
|
29
|
+
const input = inputRef.current;
|
|
30
|
+
if (input)
|
|
31
|
+
selectionRef.current = [input.selectionStart ?? value.length, input.selectionEnd ?? value.length];
|
|
32
|
+
};
|
|
33
|
+
const insert = (letter) => {
|
|
34
|
+
const [start, end] = selectionRef.current ?? [value.length, value.length];
|
|
35
|
+
onValueChange(value.slice(0, start) + letter + value.slice(end));
|
|
36
|
+
const caret = start + letter.length;
|
|
37
|
+
selectionRef.current = [caret, caret];
|
|
38
|
+
requestAnimationFrame(() => {
|
|
39
|
+
inputRef.current?.focus();
|
|
40
|
+
inputRef.current?.setSelectionRange(caret, caret);
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
const recent = onPick ? history.filter((n) => n.toLowerCase() !== value.trim().toLowerCase()) : [];
|
|
44
|
+
return (_jsxs("div", { className: `w-full min-w-0 space-y-2 ${wrapperClassName}`, children: [_jsxs("div", { className: "relative", children: [_jsx("input", { ref: inputRef, value: value, onChange: (e) => onValueChange(e.target.value), onSelect: (e) => { remember(); onSelect?.(e); }, onBlur: (e) => { remember(); onBlur?.(e); }, ...FORGE_NO_AUTOFILL, spellCheck: false, className: `flex h-9 w-full rounded-md border border-input bg-transparent py-1 pl-3 pr-11 text-base shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm ${className}`, ...inputProps }), _jsxs(Popover.Root, { children: [_jsx(Popover.Trigger, { type: "button", disabled: inputProps.disabled, className: "absolute right-0.5 top-1/2 inline-flex h-8 w-9 -translate-y-1/2 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", "aria-label": "Insert an accented letter", title: "Accented letters", children: _jsx("span", { "aria-hidden": true, className: "text-base leading-none", children: "\u00EF" }) }), _jsx(Popover.Portal, { children: _jsxs(Popover.Content, { align: "end", sideOffset: 4, className: "z-50 w-72 rounded-md border bg-popover p-3 text-popover-foreground shadow-md outline-none", onOpenAutoFocus: (e) => e.preventDefault(), onFocusOutside: (e) => e.preventDefault(), children: [_jsxs("div", { className: "mb-2 flex items-center justify-between", children: [_jsx("p", { className: "text-sm font-medium", children: "Accented letters" }), _jsxs("button", { type: "button", "aria-pressed": upper, onClick: () => setUpper((u) => !u), className: `inline-flex h-8 items-center gap-1 rounded-md border px-3 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring ${upper ? 'border-primary bg-primary text-primary-foreground' : 'hover:bg-accent hover:text-accent-foreground'}`, children: [_jsx(ArrowBigUp, { className: "h-4 w-4" }), "Caps"] })] }), _jsx("div", { className: "grid grid-cols-8 gap-1", role: "group", "aria-label": "Accented letters", children: FORGE_ACCENTED_LETTERS.map((letter) => {
|
|
45
|
+
// ß and ÿ have no single-letter capital in names, so they stay as they are.
|
|
46
|
+
const shown = upper && letter !== 'ß' && letter !== 'ÿ' ? letter.toUpperCase() : letter;
|
|
47
|
+
return (_jsx("button", { type: "button", onClick: () => insert(shown), className: "h-8 rounded-md border text-base hover:bg-accent hover:text-accent-foreground focus-visible:outline-2 focus-visible:outline-ring", children: shown }, letter));
|
|
48
|
+
}) })] }) })] })] }), recent.length > 0 && (_jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [_jsx("span", { className: "text-xs text-muted-foreground", children: "Recent:" }), recent.map((name) => (_jsxs("span", { className: "inline-flex items-center rounded-full border text-xs", children: [_jsx("button", { type: "button", onClick: () => onPick?.(name), className: `py-1 pl-2.5 hover:text-primary focus-visible:outline-2 focus-visible:outline-ring ${onForget ? 'rounded-l-full pr-1.5' : 'rounded-full pr-2.5'}`, title: `Use ${name}`, children: name }), onForget && (_jsx("button", { type: "button", onClick: () => onForget(name), "aria-label": `Remove ${name} from recent names`, className: "rounded-r-full py-1 pl-0.5 pr-2 text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-ring", children: _jsx(X, { className: "h-3 w-3" }) }))] }, name)))] }))] }));
|
|
49
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xforge-dev/forge-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Shared auth, app registry and UI for xForge apps",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"@radix-ui/react-dialog": "^1",
|
|
41
41
|
"@radix-ui/react-dropdown-menu": "^2",
|
|
42
|
+
"@radix-ui/react-popover": "^1",
|
|
42
43
|
"@supabase/ssr": "^0.12",
|
|
43
44
|
"@supabase/supabase-js": "^2",
|
|
44
45
|
"lucide-react": "^1",
|
|
@@ -51,6 +52,7 @@
|
|
|
51
52
|
"devDependencies": {
|
|
52
53
|
"@radix-ui/react-dialog": "^1.3.1",
|
|
53
54
|
"@radix-ui/react-dropdown-menu": "^2.2.1",
|
|
55
|
+
"@radix-ui/react-popover": "^1.3.1",
|
|
54
56
|
"@supabase/ssr": "^0.12.7",
|
|
55
57
|
"@supabase/supabase-js": "^2.118.0",
|
|
56
58
|
"@testing-library/dom": "^10.4.2",
|