@flopay/ui 0.3.0 → 0.5.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 +52 -0
- package/dist/annotations/index.cjs +223 -0
- package/dist/annotations/index.cjs.map +1 -0
- package/dist/annotations/index.d.cts +115 -0
- package/dist/annotations/index.d.ts +115 -0
- package/dist/annotations/index.mjs +190 -0
- package/dist/annotations/index.mjs.map +1 -0
- package/dist/index.cjs +90 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +54 -8
- package/dist/index.d.ts +54 -8
- package/dist/index.mjs +85 -13
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -4
- package/styles/annotations.css +124 -0
- package/styles/components.css +158 -3
- package/styles/preset.css +2 -0
- package/styles/tokens.css +8 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ClassValue } from 'clsx';
|
|
2
2
|
import * as react from 'react';
|
|
3
|
-
import { HTMLAttributes, ButtonHTMLAttributes, ReactNode } from 'react';
|
|
3
|
+
import { HTMLAttributes, ButtonHTMLAttributes, ReactNode, InputHTMLAttributes, LabelHTMLAttributes } from 'react';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Merge class names, resolving conflicting Tailwind utilities (last wins).
|
|
@@ -9,27 +9,73 @@ import { HTMLAttributes, ButtonHTMLAttributes, ReactNode } from 'react';
|
|
|
9
9
|
*/
|
|
10
10
|
declare function cn(...inputs: ClassValue[]): string;
|
|
11
11
|
|
|
12
|
+
type BadgeVariant = 'neutral' | 'success' | 'warning' | 'danger' | 'info';
|
|
12
13
|
interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
|
|
13
14
|
/** Render a leading status dot. */
|
|
14
15
|
dot?: boolean;
|
|
16
|
+
/** Semantic color. `neutral` is the default outlined pill. */
|
|
17
|
+
variant?: BadgeVariant;
|
|
15
18
|
}
|
|
16
|
-
/** Pill badge, optionally with a
|
|
17
|
-
declare function Badge({ dot, className, children, ...props }: BadgeProps): react.JSX.Element;
|
|
19
|
+
/** Pill badge — neutral by default, or a semantic variant, optionally with a dot. */
|
|
20
|
+
declare function Badge({ dot, variant, className, children, ...props }: BadgeProps): react.JSX.Element;
|
|
18
21
|
|
|
19
22
|
type ButtonVariant = 'primary' | 'secondary' | 'outline';
|
|
23
|
+
type ButtonSize = 'md' | 'sm';
|
|
20
24
|
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
21
25
|
variant?: ButtonVariant;
|
|
26
|
+
size?: ButtonSize;
|
|
22
27
|
}
|
|
23
|
-
declare function Button({ variant, className, type, ...props }: ButtonProps): react.JSX.Element;
|
|
28
|
+
declare function Button({ variant, size, className, type, ...props }: ButtonProps): react.JSX.Element;
|
|
24
29
|
|
|
25
|
-
type
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
type CardPadding = 'none' | 'sm' | 'md' | 'lg';
|
|
31
|
+
interface CardProps extends HTMLAttributes<HTMLDivElement> {
|
|
32
|
+
/** Inner padding. Defaults to `md` (28px); use `none` to supply your own. */
|
|
33
|
+
padding?: CardPadding;
|
|
34
|
+
/** Add pointer cursor + hover elevation (for clickable cards). */
|
|
35
|
+
interactive?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/** Bordered surface card. Static by default; opt into hover with `interactive`. */
|
|
38
|
+
declare function Card({ padding, interactive, className, ...props }: CardProps): react.JSX.Element;
|
|
28
39
|
|
|
29
40
|
type ContainerProps = HTMLAttributes<HTMLDivElement>;
|
|
30
41
|
/** Centered, max-width content wrapper (var(--max-w)) with horizontal padding. */
|
|
31
42
|
declare function Container({ className, ...props }: ContainerProps): react.JSX.Element;
|
|
32
43
|
|
|
44
|
+
interface SpinnerProps extends HTMLAttributes<HTMLSpanElement> {
|
|
45
|
+
/** Wrap in a centered, padded block (for loading states in a panel). */
|
|
46
|
+
center?: boolean;
|
|
47
|
+
}
|
|
48
|
+
/** Brand-colored loading spinner. */
|
|
49
|
+
declare function Spinner({ center, className, ...props }: SpinnerProps): react.JSX.Element;
|
|
50
|
+
interface EmptyStateProps {
|
|
51
|
+
title: ReactNode;
|
|
52
|
+
description?: ReactNode;
|
|
53
|
+
/** Optional icon rendered in the circular badge above the title. */
|
|
54
|
+
icon?: ReactNode;
|
|
55
|
+
className?: string;
|
|
56
|
+
}
|
|
57
|
+
/** Centered "nothing here yet" placeholder for empty lists/tables. */
|
|
58
|
+
declare function EmptyState({ title, description, icon, className }: EmptyStateProps): react.JSX.Element;
|
|
59
|
+
|
|
60
|
+
type InputProps = InputHTMLAttributes<HTMLInputElement>;
|
|
61
|
+
/** Token-aligned text input (the `.field` look: border, surface, brand focus ring). */
|
|
62
|
+
declare function Input({ className, ...props }: InputProps): react.JSX.Element;
|
|
63
|
+
type LabelProps = LabelHTMLAttributes<HTMLLabelElement>;
|
|
64
|
+
declare function Label({ className, ...props }: LabelProps): react.JSX.Element;
|
|
65
|
+
interface FieldProps {
|
|
66
|
+
label?: ReactNode;
|
|
67
|
+
/** Helper text shown below the control (hidden when `error` is set). */
|
|
68
|
+
hint?: ReactNode;
|
|
69
|
+
/** Error text shown below the control (takes precedence over `hint`). */
|
|
70
|
+
error?: ReactNode;
|
|
71
|
+
/** `htmlFor` for the label, matching the control's id. */
|
|
72
|
+
htmlFor?: string;
|
|
73
|
+
className?: string;
|
|
74
|
+
children: ReactNode;
|
|
75
|
+
}
|
|
76
|
+
/** Labelled field wrapper: optional label above and hint/error below the control. */
|
|
77
|
+
declare function Field({ label, hint, error, htmlFor, className, children }: FieldProps): react.JSX.Element;
|
|
78
|
+
|
|
33
79
|
type SectionProps = HTMLAttributes<HTMLElement>;
|
|
34
80
|
/** Vertical page section with max-width and standard padding. */
|
|
35
81
|
declare function Section({ className, ...props }: SectionProps): react.JSX.Element;
|
|
@@ -43,4 +89,4 @@ interface SectionHeaderProps {
|
|
|
43
89
|
/** Centered section heading: optional label, title, optional description. */
|
|
44
90
|
declare function SectionHeader({ label, title, description, className }: SectionHeaderProps): react.JSX.Element;
|
|
45
91
|
|
|
46
|
-
export { Badge, type BadgeProps, Button, type ButtonProps, type ButtonVariant, Card, type CardProps, Container, type ContainerProps, Section, SectionHeader, type SectionHeaderProps, type SectionProps, cn };
|
|
92
|
+
export { Badge, type BadgeProps, type BadgeVariant, Button, type ButtonProps, type ButtonSize, type ButtonVariant, Card, type CardPadding, type CardProps, Container, type ContainerProps, EmptyState, type EmptyStateProps, Field, type FieldProps, Input, type InputProps, Label, type LabelProps, Section, SectionHeader, type SectionHeaderProps, type SectionProps, Spinner, type SpinnerProps, cn };
|
package/dist/index.mjs
CHANGED
|
@@ -4,8 +4,21 @@ import {
|
|
|
4
4
|
|
|
5
5
|
// src/components/badge.tsx
|
|
6
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
var variantClass = {
|
|
8
|
+
neutral: "",
|
|
9
|
+
success: "badge-success",
|
|
10
|
+
warning: "badge-warning",
|
|
11
|
+
danger: "badge-danger",
|
|
12
|
+
info: "badge-info"
|
|
13
|
+
};
|
|
14
|
+
function Badge({
|
|
15
|
+
dot = false,
|
|
16
|
+
variant = "neutral",
|
|
17
|
+
className,
|
|
18
|
+
children,
|
|
19
|
+
...props
|
|
20
|
+
}) {
|
|
21
|
+
return /* @__PURE__ */ jsxs("span", { className: cn("badge", variantClass[variant], className), ...props, children: [
|
|
9
22
|
dot ? /* @__PURE__ */ jsx("span", { className: "badge-dot" }) : null,
|
|
10
23
|
children
|
|
11
24
|
] });
|
|
@@ -13,19 +26,43 @@ function Badge({ dot = false, className, children, ...props }) {
|
|
|
13
26
|
|
|
14
27
|
// src/components/button.tsx
|
|
15
28
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
16
|
-
var
|
|
29
|
+
var variantClass2 = {
|
|
17
30
|
primary: "btn-primary",
|
|
18
31
|
secondary: "btn-secondary",
|
|
19
32
|
outline: "btn-outline"
|
|
20
33
|
};
|
|
21
|
-
function Button({
|
|
22
|
-
|
|
34
|
+
function Button({
|
|
35
|
+
variant = "primary",
|
|
36
|
+
size = "md",
|
|
37
|
+
className,
|
|
38
|
+
type = "button",
|
|
39
|
+
...props
|
|
40
|
+
}) {
|
|
41
|
+
return /* @__PURE__ */ jsx2(
|
|
42
|
+
"button",
|
|
43
|
+
{
|
|
44
|
+
type,
|
|
45
|
+
className: cn(variantClass2[variant], size === "sm" && "btn-sm", className),
|
|
46
|
+
...props
|
|
47
|
+
}
|
|
48
|
+
);
|
|
23
49
|
}
|
|
24
50
|
|
|
25
51
|
// src/components/card.tsx
|
|
26
52
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
27
|
-
function Card({ className, ...props }) {
|
|
28
|
-
return /* @__PURE__ */ jsx3(
|
|
53
|
+
function Card({ padding = "md", interactive = false, className, ...props }) {
|
|
54
|
+
return /* @__PURE__ */ jsx3(
|
|
55
|
+
"div",
|
|
56
|
+
{
|
|
57
|
+
className: cn(
|
|
58
|
+
"card",
|
|
59
|
+
padding !== "none" && `card-pad-${padding}`,
|
|
60
|
+
interactive && "card-interactive",
|
|
61
|
+
className
|
|
62
|
+
),
|
|
63
|
+
...props
|
|
64
|
+
}
|
|
65
|
+
);
|
|
29
66
|
}
|
|
30
67
|
|
|
31
68
|
// src/components/container.tsx
|
|
@@ -34,16 +71,46 @@ function Container({ className, ...props }) {
|
|
|
34
71
|
return /* @__PURE__ */ jsx4("div", { className: cn("container", className), ...props });
|
|
35
72
|
}
|
|
36
73
|
|
|
37
|
-
// src/components/
|
|
74
|
+
// src/components/feedback.tsx
|
|
38
75
|
import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
76
|
+
function Spinner({ center = false, className, ...props }) {
|
|
77
|
+
const spinner = /* @__PURE__ */ jsx5("span", { className: cn("spinner", className), role: "status", "aria-label": "Loading", ...props });
|
|
78
|
+
return center ? /* @__PURE__ */ jsx5("div", { className: "spinner-center", children: spinner }) : spinner;
|
|
79
|
+
}
|
|
80
|
+
function EmptyState({ title, description, icon, className }) {
|
|
81
|
+
return /* @__PURE__ */ jsxs2("div", { className: cn("empty-state", className), children: [
|
|
82
|
+
icon ? /* @__PURE__ */ jsx5("div", { className: "empty-state-icon", children: icon }) : null,
|
|
83
|
+
/* @__PURE__ */ jsx5("h3", { className: "empty-state-title", children: title }),
|
|
84
|
+
description ? /* @__PURE__ */ jsx5("p", { className: "empty-state-desc", children: description }) : null
|
|
85
|
+
] });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/components/field.tsx
|
|
89
|
+
import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
90
|
+
function Input({ className, ...props }) {
|
|
91
|
+
return /* @__PURE__ */ jsx6("input", { className: cn("field", className), ...props });
|
|
92
|
+
}
|
|
93
|
+
function Label({ className, ...props }) {
|
|
94
|
+
return /* @__PURE__ */ jsx6("label", { className: cn("field-label", className), ...props });
|
|
95
|
+
}
|
|
96
|
+
function Field({ label, hint, error, htmlFor, className, children }) {
|
|
97
|
+
return /* @__PURE__ */ jsxs3("div", { className: cn("field-group", className), children: [
|
|
98
|
+
label ? /* @__PURE__ */ jsx6(Label, { htmlFor, children: label }) : null,
|
|
99
|
+
children,
|
|
100
|
+
error ? /* @__PURE__ */ jsx6("span", { className: "field-error", children: error }) : hint ? /* @__PURE__ */ jsx6("span", { className: "field-hint", children: hint }) : null
|
|
101
|
+
] });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/components/section.tsx
|
|
105
|
+
import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
39
106
|
function Section({ className, ...props }) {
|
|
40
|
-
return /* @__PURE__ */
|
|
107
|
+
return /* @__PURE__ */ jsx7("section", { className: cn("section", className), ...props });
|
|
41
108
|
}
|
|
42
109
|
function SectionHeader({ label, title, description, className }) {
|
|
43
|
-
return /* @__PURE__ */
|
|
44
|
-
label ? /* @__PURE__ */
|
|
45
|
-
/* @__PURE__ */
|
|
46
|
-
description ? /* @__PURE__ */
|
|
110
|
+
return /* @__PURE__ */ jsxs4("div", { className: cn("section-center", className), children: [
|
|
111
|
+
label ? /* @__PURE__ */ jsx7("div", { className: "section-label", children: label }) : null,
|
|
112
|
+
/* @__PURE__ */ jsx7("h2", { className: "section-title", children: title }),
|
|
113
|
+
description ? /* @__PURE__ */ jsx7("p", { className: "section-desc", children: description }) : null
|
|
47
114
|
] });
|
|
48
115
|
}
|
|
49
116
|
export {
|
|
@@ -51,8 +118,13 @@ export {
|
|
|
51
118
|
Button,
|
|
52
119
|
Card,
|
|
53
120
|
Container,
|
|
121
|
+
EmptyState,
|
|
122
|
+
Field,
|
|
123
|
+
Input,
|
|
124
|
+
Label,
|
|
54
125
|
Section,
|
|
55
126
|
SectionHeader,
|
|
127
|
+
Spinner,
|
|
56
128
|
cn
|
|
57
129
|
};
|
|
58
130
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/badge.tsx","../src/components/button.tsx","../src/components/card.tsx","../src/components/container.tsx","../src/components/section.tsx"],"sourcesContent":["import type { HTMLAttributes } from 'react';\nimport { cn } from '../cn';\n\nexport interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {\n /** Render a leading status dot. */\n dot?: boolean;\n}\n\n/** Pill badge, optionally with a
|
|
1
|
+
{"version":3,"sources":["../src/components/badge.tsx","../src/components/button.tsx","../src/components/card.tsx","../src/components/container.tsx","../src/components/feedback.tsx","../src/components/field.tsx","../src/components/section.tsx"],"sourcesContent":["import type { HTMLAttributes } from 'react';\nimport { cn } from '../cn';\n\nexport type BadgeVariant = 'neutral' | 'success' | 'warning' | 'danger' | 'info';\n\nexport interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {\n /** Render a leading status dot. */\n dot?: boolean;\n /** Semantic color. `neutral` is the default outlined pill. */\n variant?: BadgeVariant;\n}\n\nconst variantClass: Record<BadgeVariant, string> = {\n neutral: '',\n success: 'badge-success',\n warning: 'badge-warning',\n danger: 'badge-danger',\n info: 'badge-info',\n};\n\n/** Pill badge — neutral by default, or a semantic variant, optionally with a dot. */\nexport function Badge({\n dot = false,\n variant = 'neutral',\n className,\n children,\n ...props\n}: BadgeProps) {\n return (\n <span className={cn('badge', variantClass[variant], className)} {...props}>\n {dot ? <span className=\"badge-dot\" /> : null}\n {children}\n </span>\n );\n}\n","import type { ButtonHTMLAttributes } from 'react';\nimport { cn } from '../cn';\n\nexport type ButtonVariant = 'primary' | 'secondary' | 'outline';\nexport type ButtonSize = 'md' | 'sm';\n\nexport interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: ButtonVariant;\n size?: ButtonSize;\n}\n\nconst variantClass: Record<ButtonVariant, string> = {\n primary: 'btn-primary',\n secondary: 'btn-secondary',\n outline: 'btn-outline',\n};\n\nexport function Button({\n variant = 'primary',\n size = 'md',\n className,\n type = 'button',\n ...props\n}: ButtonProps) {\n return (\n <button\n type={type}\n className={cn(variantClass[variant], size === 'sm' && 'btn-sm', className)}\n {...props}\n />\n );\n}\n","import type { HTMLAttributes } from 'react';\nimport { cn } from '../cn';\n\nexport type CardPadding = 'none' | 'sm' | 'md' | 'lg';\n\nexport interface CardProps extends HTMLAttributes<HTMLDivElement> {\n /** Inner padding. Defaults to `md` (28px); use `none` to supply your own. */\n padding?: CardPadding;\n /** Add pointer cursor + hover elevation (for clickable cards). */\n interactive?: boolean;\n}\n\n/** Bordered surface card. Static by default; opt into hover with `interactive`. */\nexport function Card({ padding = 'md', interactive = false, className, ...props }: CardProps) {\n return (\n <div\n className={cn(\n 'card',\n padding !== 'none' && `card-pad-${padding}`,\n interactive && 'card-interactive',\n className,\n )}\n {...props}\n />\n );\n}\n","import type { HTMLAttributes } from 'react';\nimport { cn } from '../cn';\n\nexport type ContainerProps = HTMLAttributes<HTMLDivElement>;\n\n/** Centered, max-width content wrapper (var(--max-w)) with horizontal padding. */\nexport function Container({ className, ...props }: ContainerProps) {\n return <div className={cn('container', className)} {...props} />;\n}\n","import type { HTMLAttributes, ReactNode } from 'react';\nimport { cn } from '../cn';\n\nexport interface SpinnerProps extends HTMLAttributes<HTMLSpanElement> {\n /** Wrap in a centered, padded block (for loading states in a panel). */\n center?: boolean;\n}\n\n/** Brand-colored loading spinner. */\nexport function Spinner({ center = false, className, ...props }: SpinnerProps) {\n const spinner = (\n <span className={cn('spinner', className)} role=\"status\" aria-label=\"Loading\" {...props} />\n );\n return center ? <div className=\"spinner-center\">{spinner}</div> : spinner;\n}\n\nexport interface EmptyStateProps {\n title: ReactNode;\n description?: ReactNode;\n /** Optional icon rendered in the circular badge above the title. */\n icon?: ReactNode;\n className?: string;\n}\n\n/** Centered \"nothing here yet\" placeholder for empty lists/tables. */\nexport function EmptyState({ title, description, icon, className }: EmptyStateProps) {\n return (\n <div className={cn('empty-state', className)}>\n {icon ? <div className=\"empty-state-icon\">{icon}</div> : null}\n <h3 className=\"empty-state-title\">{title}</h3>\n {description ? <p className=\"empty-state-desc\">{description}</p> : null}\n </div>\n );\n}\n","import type { InputHTMLAttributes, LabelHTMLAttributes, ReactNode } from 'react';\nimport { cn } from '../cn';\n\nexport type InputProps = InputHTMLAttributes<HTMLInputElement>;\n\n/** Token-aligned text input (the `.field` look: border, surface, brand focus ring). */\nexport function Input({ className, ...props }: InputProps) {\n return <input className={cn('field', className)} {...props} />;\n}\n\nexport type LabelProps = LabelHTMLAttributes<HTMLLabelElement>;\n\nexport function Label({ className, ...props }: LabelProps) {\n return <label className={cn('field-label', className)} {...props} />;\n}\n\nexport interface FieldProps {\n label?: ReactNode;\n /** Helper text shown below the control (hidden when `error` is set). */\n hint?: ReactNode;\n /** Error text shown below the control (takes precedence over `hint`). */\n error?: ReactNode;\n /** `htmlFor` for the label, matching the control's id. */\n htmlFor?: string;\n className?: string;\n children: ReactNode;\n}\n\n/** Labelled field wrapper: optional label above and hint/error below the control. */\nexport function Field({ label, hint, error, htmlFor, className, children }: FieldProps) {\n return (\n <div className={cn('field-group', className)}>\n {label ? <Label htmlFor={htmlFor}>{label}</Label> : null}\n {children}\n {error ? (\n <span className=\"field-error\">{error}</span>\n ) : hint ? (\n <span className=\"field-hint\">{hint}</span>\n ) : null}\n </div>\n );\n}\n","import type { HTMLAttributes, ReactNode } from 'react';\nimport { cn } from '../cn';\n\nexport type SectionProps = HTMLAttributes<HTMLElement>;\n\n/** Vertical page section with max-width and standard padding. */\nexport function Section({ className, ...props }: SectionProps) {\n return <section className={cn('section', className)} {...props} />;\n}\n\nexport interface SectionHeaderProps {\n /** Small eyebrow label above the title (brand colored). */\n label?: ReactNode;\n title: ReactNode;\n description?: ReactNode;\n className?: string;\n}\n\n/** Centered section heading: optional label, title, optional description. */\nexport function SectionHeader({ label, title, description, className }: SectionHeaderProps) {\n return (\n <div className={cn('section-center', className)}>\n {label ? <div className=\"section-label\">{label}</div> : null}\n <h2 className=\"section-title\">{title}</h2>\n {description ? <p className=\"section-desc\">{description}</p> : null}\n </div>\n );\n}\n"],"mappings":"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","names":["jsx","variantClass","jsx","jsx","jsx","jsxs","jsx","jsxs","jsx","jsxs"]}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flopay/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"packageManager": "pnpm@
|
|
5
|
+
"packageManager": "pnpm@11.17.0",
|
|
6
6
|
"description": "FloPay shared design system — design tokens, a Tailwind v4 preset, and React primitives shared across landing, demo, docs, and dashboard.",
|
|
7
7
|
"license": "UNLICENSED",
|
|
8
8
|
"publishConfig": {
|
|
@@ -27,12 +27,18 @@
|
|
|
27
27
|
"import": "./dist/marketing/index.mjs",
|
|
28
28
|
"require": "./dist/marketing/index.cjs"
|
|
29
29
|
},
|
|
30
|
+
"./annotations": {
|
|
31
|
+
"types": "./dist/annotations/index.d.ts",
|
|
32
|
+
"import": "./dist/annotations/index.mjs",
|
|
33
|
+
"require": "./dist/annotations/index.cjs"
|
|
34
|
+
},
|
|
30
35
|
"./styles": "./styles/index.css",
|
|
31
36
|
"./styles/index.css": "./styles/index.css",
|
|
32
37
|
"./styles/tokens.css": "./styles/tokens.css",
|
|
33
38
|
"./styles/reset.css": "./styles/reset.css",
|
|
34
39
|
"./styles/components.css": "./styles/components.css",
|
|
35
40
|
"./styles/preset.css": "./styles/preset.css",
|
|
41
|
+
"./styles/annotations.css": "./styles/annotations.css",
|
|
36
42
|
"./logo.png": "./assets/logo.png",
|
|
37
43
|
"./package.json": "./package.json"
|
|
38
44
|
},
|
|
@@ -68,10 +74,10 @@
|
|
|
68
74
|
"tailwind-merge": "^3.6.0"
|
|
69
75
|
},
|
|
70
76
|
"devDependencies": {
|
|
71
|
-
"@biomejs/biome": "^2.
|
|
77
|
+
"@biomejs/biome": "^2.5.3",
|
|
72
78
|
"@types/react": "^19.2.15",
|
|
73
79
|
"@types/react-dom": "^19.2.3",
|
|
74
|
-
"next": "^16.2.
|
|
80
|
+
"next": "^16.2.10",
|
|
75
81
|
"react": "^19.2.6",
|
|
76
82
|
"react-dom": "^19.2.6",
|
|
77
83
|
"tsup": "^8.3.0",
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/* @flopay/ui — hand-drawn annotation layer.
|
|
2
|
+
*
|
|
3
|
+
* Marker-style notes and arrows drawn over a UI, for guided demos and product
|
|
4
|
+
* tours. Opt-in, since the arrow geometry is a few KB that most pages don't
|
|
5
|
+
* need:
|
|
6
|
+
*
|
|
7
|
+
* @import "@flopay/ui/styles/annotations.css";
|
|
8
|
+
*
|
|
9
|
+
* Backs the components exported from `@flopay/ui/annotations`, and depends on
|
|
10
|
+
* tokens.css custom properties. */
|
|
11
|
+
|
|
12
|
+
:root {
|
|
13
|
+
/* Brand blue by default, in a handwriting face, so notes read as marker over
|
|
14
|
+
the UI rather than as part of it. Override either per app. */
|
|
15
|
+
--annotation-ink: var(--brand);
|
|
16
|
+
--annotation-font: 'Bradley Hand', 'Segoe Print', 'Chalkboard SE', 'Comic Sans MS', cursive;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.annotation-stage {
|
|
20
|
+
width: 100%;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* The positioning context for `pos`. Notes overhang it into the outer stage. */
|
|
24
|
+
.annotation-stage-inner {
|
|
25
|
+
position: relative;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* ── Narrow: the notes as a plain list under the content ── */
|
|
29
|
+
.annotation-layer {
|
|
30
|
+
display: none;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.annotation-list {
|
|
34
|
+
max-width: 420px;
|
|
35
|
+
margin: 18px auto 0;
|
|
36
|
+
padding-top: 14px;
|
|
37
|
+
border-top: 1px dashed var(--border-hover);
|
|
38
|
+
list-style: none;
|
|
39
|
+
display: flex;
|
|
40
|
+
flex-direction: column;
|
|
41
|
+
gap: 7px;
|
|
42
|
+
font-family: var(--annotation-font);
|
|
43
|
+
font-size: 16px;
|
|
44
|
+
line-height: 1.45;
|
|
45
|
+
color: var(--annotation-ink);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.annotation-list li::before {
|
|
49
|
+
content: '→ ';
|
|
50
|
+
opacity: 0.7;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* ── Wide: notes float around the content ──
|
|
54
|
+
Notes overhang the stage into the surrounding margins, so what matters is
|
|
55
|
+
the room *around* the content — which belongs to an ancestor the package
|
|
56
|
+
doesn't own. A container query on the stage would measure the content box
|
|
57
|
+
instead and never match. Hence a viewport query.
|
|
58
|
+
1120px is where a ~720px content box has room for 200px notes either side.
|
|
59
|
+
An app in a constrained layout (a sidebar, a split view) should re-declare
|
|
60
|
+
this one block at its own breakpoint; a custom property can't be used in a
|
|
61
|
+
media condition. */
|
|
62
|
+
@media (min-width: 1120px) {
|
|
63
|
+
.annotation-layer {
|
|
64
|
+
display: block;
|
|
65
|
+
position: absolute;
|
|
66
|
+
inset: 0;
|
|
67
|
+
pointer-events: none;
|
|
68
|
+
/* Above the content so an arrow tip can cross onto its target rather than
|
|
69
|
+
stopping at the edge. */
|
|
70
|
+
z-index: 2;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.annotation-list {
|
|
74
|
+
display: none;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.annotation {
|
|
78
|
+
position: absolute;
|
|
79
|
+
width: 200px;
|
|
80
|
+
display: flex;
|
|
81
|
+
flex-direction: column;
|
|
82
|
+
gap: 1px;
|
|
83
|
+
transform: rotate(var(--annotation-tilt, 0deg));
|
|
84
|
+
font-family: var(--annotation-font);
|
|
85
|
+
font-size: 18px;
|
|
86
|
+
line-height: 1.3;
|
|
87
|
+
color: var(--annotation-ink);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Arrow above the text for upward variants, below it otherwise. */
|
|
91
|
+
.annotation[data-flip] {
|
|
92
|
+
flex-direction: column-reverse;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Text rags toward whatever the arrow points at. */
|
|
96
|
+
.annotation[data-side='right'] { text-align: right; }
|
|
97
|
+
.annotation[data-side='left'] { text-align: left; }
|
|
98
|
+
.annotation[data-side='center'] { text-align: center; }
|
|
99
|
+
|
|
100
|
+
/* `position: relative` on both halves is what lets `posText` / `posArrow`
|
|
101
|
+
nudge one without the other. Offsets shift them off the spot `pos` put
|
|
102
|
+
them, leaving the flex layout — and therefore each other — undisturbed. */
|
|
103
|
+
.annotation-text {
|
|
104
|
+
position: relative;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* `--annotation-arrow-rotate` re-aims the curve and the flips mirror it, so
|
|
108
|
+
one drawing covers several approach directions without a new curve. Those
|
|
109
|
+
are visual only — the layout box stays put, which is why a re-aimed arrow
|
|
110
|
+
overhangs it. Scale sits to the right of rotate so it applies first: you
|
|
111
|
+
pick the mirrored shape, then aim it. */
|
|
112
|
+
.annotation-arrow {
|
|
113
|
+
position: relative;
|
|
114
|
+
width: calc(160px * var(--annotation-arrow-scale, 1));
|
|
115
|
+
height: auto;
|
|
116
|
+
opacity: 0.9;
|
|
117
|
+
transform: rotate(var(--annotation-arrow-rotate, 0deg))
|
|
118
|
+
scale(var(--annotation-arrow-flip-x, 1), var(--annotation-arrow-flip-y, 1));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.annotation[data-side='right'] .annotation-arrow { align-self: flex-end; }
|
|
122
|
+
.annotation[data-side='left'] .annotation-arrow { align-self: flex-start; }
|
|
123
|
+
.annotation[data-side='center'] .annotation-arrow { align-self: center; }
|
|
124
|
+
}
|
package/styles/components.css
CHANGED
|
@@ -116,6 +116,13 @@
|
|
|
116
116
|
border-color: var(--border-hover);
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
/* Small / dense modifier — compose with a variant (e.g. "btn-primary btn-sm"). */
|
|
120
|
+
.btn-sm {
|
|
121
|
+
gap: 5px;
|
|
122
|
+
padding: 8px 14px;
|
|
123
|
+
font-size: 13px;
|
|
124
|
+
}
|
|
125
|
+
|
|
119
126
|
/* ── Badge ── */
|
|
120
127
|
.badge {
|
|
121
128
|
display: inline-flex;
|
|
@@ -137,16 +144,58 @@
|
|
|
137
144
|
flex-shrink: 0;
|
|
138
145
|
}
|
|
139
146
|
|
|
147
|
+
/* Semantic variants — compose with .badge (e.g. "badge badge-success"). */
|
|
148
|
+
.badge-success {
|
|
149
|
+
border-color: transparent;
|
|
150
|
+
background: color-mix(in srgb, var(--success) 12%, transparent);
|
|
151
|
+
color: var(--success);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.badge-warning {
|
|
155
|
+
border-color: transparent;
|
|
156
|
+
background: var(--warning-bg);
|
|
157
|
+
color: var(--warning-fg);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.badge-danger {
|
|
161
|
+
border-color: transparent;
|
|
162
|
+
background: color-mix(in srgb, var(--danger) 12%, transparent);
|
|
163
|
+
color: var(--danger);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.badge-info {
|
|
167
|
+
border-color: transparent;
|
|
168
|
+
background: color-mix(in srgb, var(--brand) 12%, transparent);
|
|
169
|
+
color: var(--brand);
|
|
170
|
+
}
|
|
171
|
+
|
|
140
172
|
/* ── Card ── */
|
|
173
|
+
/* Surface only — opt into padding (.card-pad-*) and hover (.card-interactive). */
|
|
141
174
|
.card {
|
|
142
|
-
background: var(--bg);
|
|
175
|
+
background: var(--bg-card);
|
|
143
176
|
border: 1px solid var(--border);
|
|
144
177
|
border-radius: var(--radius-lg);
|
|
145
|
-
padding: 28px;
|
|
146
178
|
transition: box-shadow 0.2s, border-color 0.2s;
|
|
147
179
|
}
|
|
148
180
|
|
|
149
|
-
.card
|
|
181
|
+
.card-pad-sm {
|
|
182
|
+
padding: 16px;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.card-pad-md {
|
|
186
|
+
padding: 28px;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.card-pad-lg {
|
|
190
|
+
padding: 36px;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.card-interactive {
|
|
194
|
+
cursor: pointer;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.card-interactive:hover {
|
|
198
|
+
border-color: var(--border-hover);
|
|
150
199
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
|
|
151
200
|
}
|
|
152
201
|
|
|
@@ -334,3 +383,109 @@
|
|
|
334
383
|
gap: 12px;
|
|
335
384
|
}
|
|
336
385
|
}
|
|
386
|
+
|
|
387
|
+
/* ── Form fields ── */
|
|
388
|
+
.field {
|
|
389
|
+
width: 100%;
|
|
390
|
+
border: 1px solid var(--border);
|
|
391
|
+
background: var(--bg-card);
|
|
392
|
+
border-radius: var(--radius);
|
|
393
|
+
padding: 9px 12px;
|
|
394
|
+
font-family: var(--font);
|
|
395
|
+
font-size: 14px;
|
|
396
|
+
color: var(--text);
|
|
397
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.field::placeholder {
|
|
401
|
+
color: var(--text-tertiary);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.field:focus {
|
|
405
|
+
outline: none;
|
|
406
|
+
border-color: var(--brand);
|
|
407
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 20%, transparent);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.field[aria-invalid='true'] {
|
|
411
|
+
border-color: var(--danger);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.field[aria-invalid='true']:focus {
|
|
415
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 20%, transparent);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.field-group {
|
|
419
|
+
display: flex;
|
|
420
|
+
flex-direction: column;
|
|
421
|
+
gap: 6px;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.field-label {
|
|
425
|
+
font-size: 13px;
|
|
426
|
+
font-weight: 500;
|
|
427
|
+
color: var(--text);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.field-hint {
|
|
431
|
+
font-size: 12px;
|
|
432
|
+
color: var(--text-tertiary);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
.field-error {
|
|
436
|
+
font-size: 12px;
|
|
437
|
+
color: var(--danger);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/* ── Spinner ── */
|
|
441
|
+
.spinner {
|
|
442
|
+
display: inline-block;
|
|
443
|
+
width: 24px;
|
|
444
|
+
height: 24px;
|
|
445
|
+
border: 2px solid var(--border);
|
|
446
|
+
border-top-color: var(--brand);
|
|
447
|
+
border-radius: 50%;
|
|
448
|
+
animation: fp-spin 0.6s linear infinite;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
@keyframes fp-spin {
|
|
452
|
+
to {
|
|
453
|
+
transform: rotate(360deg);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
.spinner-center {
|
|
458
|
+
display: flex;
|
|
459
|
+
justify-content: center;
|
|
460
|
+
padding: 64px 0;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/* ── Empty state ── */
|
|
464
|
+
.empty-state {
|
|
465
|
+
text-align: center;
|
|
466
|
+
padding: 64px 24px;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
.empty-state-icon {
|
|
470
|
+
display: inline-flex;
|
|
471
|
+
align-items: center;
|
|
472
|
+
justify-content: center;
|
|
473
|
+
width: 48px;
|
|
474
|
+
height: 48px;
|
|
475
|
+
border-radius: 50%;
|
|
476
|
+
background: var(--bg-muted);
|
|
477
|
+
color: var(--text-tertiary);
|
|
478
|
+
margin-bottom: 16px;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.empty-state-title {
|
|
482
|
+
font-size: 14px;
|
|
483
|
+
font-weight: 500;
|
|
484
|
+
color: var(--text);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.empty-state-desc {
|
|
488
|
+
font-size: 14px;
|
|
489
|
+
color: var(--text-secondary);
|
|
490
|
+
margin-top: 4px;
|
|
491
|
+
}
|
package/styles/preset.css
CHANGED
package/styles/tokens.css
CHANGED
|
@@ -45,6 +45,11 @@
|
|
|
45
45
|
--danger: #dc2626;
|
|
46
46
|
--warning: #f5a623;
|
|
47
47
|
|
|
48
|
+
/* Readable warning pair for soft surfaces (badges/callouts) — the solid
|
|
49
|
+
--warning is too light to use as text. */
|
|
50
|
+
--warning-bg: #fffbeb;
|
|
51
|
+
--warning-fg: #b45309;
|
|
52
|
+
|
|
48
53
|
/* Radius */
|
|
49
54
|
--radius: 8px;
|
|
50
55
|
--radius-lg: 12px;
|
|
@@ -78,4 +83,7 @@
|
|
|
78
83
|
|
|
79
84
|
--accent: #fafafa;
|
|
80
85
|
--accent-hover: #e4e4e7;
|
|
86
|
+
|
|
87
|
+
--warning-bg: #451a03;
|
|
88
|
+
--warning-fg: #fcd34d;
|
|
81
89
|
}
|