@flopay/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/README.md +109 -0
- package/dist/index.cjs +94 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +46 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.mjs +61 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +66 -0
- package/styles/components.css +336 -0
- package/styles/index.css +12 -0
- package/styles/preset.css +52 -0
- package/styles/reset.css +30 -0
- package/styles/tokens.css +76 -0
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# @flopay/ui
|
|
2
|
+
|
|
3
|
+
The shared FloPay design system — the single source of truth for design tokens,
|
|
4
|
+
a Tailwind v4 theme preset, and a small set of React primitives. Consumed by
|
|
5
|
+
`landing`, `demo`, `docs`, and `dashboard`.
|
|
6
|
+
|
|
7
|
+
It is **framework-agnostic at the token layer**: the same CSS custom properties
|
|
8
|
+
drive both the plain-CSS apps (landing, demo) and the Tailwind v4 apps (docs,
|
|
9
|
+
dashboard). Change a token once here and every app inherits it.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm add @flopay/ui
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`react` and `react-dom` (`^19`) are peer dependencies (only needed if you import
|
|
18
|
+
the React components).
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### 1. Plain-CSS apps (landing, demo)
|
|
23
|
+
|
|
24
|
+
Import everything (reset + tokens + shared component classes) at the top of your
|
|
25
|
+
global stylesheet:
|
|
26
|
+
|
|
27
|
+
```css
|
|
28
|
+
/* app/globals.css */
|
|
29
|
+
@import "@flopay/ui/styles";
|
|
30
|
+
|
|
31
|
+
/* ...your page-specific CSS, using var(--brand), var(--text), etc. */
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Or cherry-pick:
|
|
35
|
+
|
|
36
|
+
```css
|
|
37
|
+
@import "@flopay/ui/styles/tokens.css"; /* :root design tokens only */
|
|
38
|
+
@import "@flopay/ui/styles/reset.css";
|
|
39
|
+
@import "@flopay/ui/styles/components.css"; /* .btn-primary, .nav, .footer, .card ... */
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 2. Tailwind v4 apps (docs, dashboard)
|
|
43
|
+
|
|
44
|
+
Add the preset after Tailwind. It registers the tokens as theme values, so
|
|
45
|
+
utilities like `bg-brand`, `text-text-secondary`, `border-border`, and
|
|
46
|
+
`font-sans` resolve to the shared tokens:
|
|
47
|
+
|
|
48
|
+
```css
|
|
49
|
+
/* app/globals.css */
|
|
50
|
+
@import "tailwindcss";
|
|
51
|
+
@import "@flopay/ui/styles/preset.css";
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
<button className="bg-brand text-bg-card rounded-lg px-6 py-3">Get started</button>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The preset uses `@theme inline`, so the `.dark` overrides in `tokens.css` apply
|
|
59
|
+
at runtime without recompiling.
|
|
60
|
+
|
|
61
|
+
### 3. React primitives
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
import { Button, Container, Section, SectionHeader, Badge, Card, cn } from "@flopay/ui";
|
|
65
|
+
|
|
66
|
+
<Container>
|
|
67
|
+
<Badge dot>Live</Badge>
|
|
68
|
+
<SectionHeader label="Pricing" title="Simple, usage-based" description="No surprises." />
|
|
69
|
+
<Card className={cn("p-10")}>…</Card>
|
|
70
|
+
<Button variant="primary">Start building</Button>
|
|
71
|
+
</Container>;
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The primitives render the shared component classes, so they work in plain-CSS
|
|
75
|
+
apps (which import `components.css`) and in Tailwind apps (where you can extend
|
|
76
|
+
them via `className` + `cn()`).
|
|
77
|
+
|
|
78
|
+
## Design tokens
|
|
79
|
+
|
|
80
|
+
`styles/tokens.css` defines surfaces, borders, text, accent, **brand
|
|
81
|
+
(`--brand: #1785e0`)**, status colors, radius, layout (`--max-w`, `--nav-h`),
|
|
82
|
+
and typography. A `.dark` block provides a dark-theme starting point.
|
|
83
|
+
|
|
84
|
+
## Develop
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pnpm install
|
|
88
|
+
pnpm dev # tsup --watch
|
|
89
|
+
pnpm build # emits dist/ (esm + cjs + d.ts); CSS in styles/ ships as-is
|
|
90
|
+
pnpm typecheck
|
|
91
|
+
pnpm biome:fix
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Release
|
|
95
|
+
|
|
96
|
+
Publishing uses **npm trusted publishing (GitHub OIDC)** — the same mechanism as
|
|
97
|
+
the `@flopay/*` SDK packages, so **no `NPM_TOKEN` is required**. One-time setup:
|
|
98
|
+
register this repo + the `publish.yml` workflow as a trusted publisher for
|
|
99
|
+
`@flopay/ui` on npmjs.org (npm package → Settings → Trusted publishers).
|
|
100
|
+
|
|
101
|
+
After that, releases are automatic:
|
|
102
|
+
|
|
103
|
+
1. Bump `version` in `package.json` on `main`.
|
|
104
|
+
2. `CI` runs (`.github/workflows/ci.yml`).
|
|
105
|
+
3. On CI success, `Publish Package` (`.github/workflows/publish.yml`) sees the
|
|
106
|
+
new version has no `v<version>` tag, `npm publish`es via OIDC (with
|
|
107
|
+
provenance), then pushes the tag.
|
|
108
|
+
|
|
109
|
+
CSS files ship raw (no build step); only the JS/types are bundled by tsup.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
Badge: () => Badge,
|
|
24
|
+
Button: () => Button,
|
|
25
|
+
Card: () => Card,
|
|
26
|
+
Container: () => Container,
|
|
27
|
+
Section: () => Section,
|
|
28
|
+
SectionHeader: () => SectionHeader,
|
|
29
|
+
cn: () => cn
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(index_exports);
|
|
32
|
+
|
|
33
|
+
// src/cn.ts
|
|
34
|
+
var import_clsx = require("clsx");
|
|
35
|
+
var import_tailwind_merge = require("tailwind-merge");
|
|
36
|
+
function cn(...inputs) {
|
|
37
|
+
return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/components/badge.tsx
|
|
41
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
42
|
+
function Badge({ dot = false, className, children, ...props }) {
|
|
43
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: cn("badge", className), ...props, children: [
|
|
44
|
+
dot ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "badge-dot" }) : null,
|
|
45
|
+
children
|
|
46
|
+
] });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/components/button.tsx
|
|
50
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
51
|
+
var variantClass = {
|
|
52
|
+
primary: "btn-primary",
|
|
53
|
+
secondary: "btn-secondary",
|
|
54
|
+
outline: "btn-outline"
|
|
55
|
+
};
|
|
56
|
+
function Button({ variant = "primary", className, type = "button", ...props }) {
|
|
57
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type, className: cn(variantClass[variant], className), ...props });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/components/card.tsx
|
|
61
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
62
|
+
function Card({ className, ...props }) {
|
|
63
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: cn("card", className), ...props });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/components/container.tsx
|
|
67
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
68
|
+
function Container({ className, ...props }) {
|
|
69
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: cn("container", className), ...props });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/components/section.tsx
|
|
73
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
74
|
+
function Section({ className, ...props }) {
|
|
75
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("section", { className: cn("section", className), ...props });
|
|
76
|
+
}
|
|
77
|
+
function SectionHeader({ label, title, description, className }) {
|
|
78
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: cn("section-center", className), children: [
|
|
79
|
+
label ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "section-label", children: label }) : null,
|
|
80
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { className: "section-title", children: title }),
|
|
81
|
+
description ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "section-desc", children: description }) : null
|
|
82
|
+
] });
|
|
83
|
+
}
|
|
84
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
85
|
+
0 && (module.exports = {
|
|
86
|
+
Badge,
|
|
87
|
+
Button,
|
|
88
|
+
Card,
|
|
89
|
+
Container,
|
|
90
|
+
Section,
|
|
91
|
+
SectionHeader,
|
|
92
|
+
cn
|
|
93
|
+
});
|
|
94
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/cn.ts","../src/components/badge.tsx","../src/components/button.tsx","../src/components/card.tsx","../src/components/container.tsx","../src/components/section.tsx"],"sourcesContent":["export { cn } from './cn';\nexport type { BadgeProps } from './components/badge';\nexport { Badge } from './components/badge';\nexport type { ButtonProps, ButtonVariant } from './components/button';\nexport { Button } from './components/button';\nexport type { CardProps } from './components/card';\nexport { Card } from './components/card';\nexport type { ContainerProps } from './components/container';\nexport { Container } from './components/container';\nexport type { SectionHeaderProps, SectionProps } from './components/section';\nexport { Section, SectionHeader } from './components/section';\n","import { type ClassValue, clsx } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Merge class names, resolving conflicting Tailwind utilities (last wins).\n * Mirrors the helper used across the FloPay frontends so consumers can extend\n * any primitive's classes safely.\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","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 leading status dot. */\nexport function Badge({ dot = false, className, children, ...props }: BadgeProps) {\n return (\n <span className={cn('badge', 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';\n\nexport interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: ButtonVariant;\n}\n\nconst variantClass: Record<ButtonVariant, string> = {\n primary: 'btn-primary',\n secondary: 'btn-secondary',\n outline: 'btn-outline',\n};\n\nexport function Button({ variant = 'primary', className, type = 'button', ...props }: ButtonProps) {\n return <button type={type} className={cn(variantClass[variant], className)} {...props} />;\n}\n","import type { HTMLAttributes } from 'react';\nimport { cn } from '../cn';\n\nexport type CardProps = HTMLAttributes<HTMLDivElement>;\n\n/** Bordered surface card with hover elevation. */\nexport function Card({ className, ...props }: CardProps) {\n return <div className={cn('card', className)} {...props} />;\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 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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,kBAAsC;AACtC,4BAAwB;AAOjB,SAAS,MAAM,QAAsB;AAC1C,aAAO,mCAAQ,kBAAK,MAAM,CAAC;AAC7B;;;ACCI;AAFG,SAAS,MAAM,EAAE,MAAM,OAAO,WAAW,UAAU,GAAG,MAAM,GAAe;AAChF,SACE,6CAAC,UAAK,WAAW,GAAG,SAAS,SAAS,GAAI,GAAG,OAC1C;AAAA,UAAM,4CAAC,UAAK,WAAU,aAAY,IAAK;AAAA,IACvC;AAAA,KACH;AAEJ;;;ACAS,IAAAA,sBAAA;AAPT,IAAM,eAA8C;AAAA,EAClD,SAAS;AAAA,EACT,WAAW;AAAA,EACX,SAAS;AACX;AAEO,SAAS,OAAO,EAAE,UAAU,WAAW,WAAW,OAAO,UAAU,GAAG,MAAM,GAAgB;AACjG,SAAO,6CAAC,YAAO,MAAY,WAAW,GAAG,aAAa,OAAO,GAAG,SAAS,GAAI,GAAG,OAAO;AACzF;;;ACVS,IAAAC,sBAAA;AADF,SAAS,KAAK,EAAE,WAAW,GAAG,MAAM,GAAc;AACvD,SAAO,6CAAC,SAAI,WAAW,GAAG,QAAQ,SAAS,GAAI,GAAG,OAAO;AAC3D;;;ACDS,IAAAC,sBAAA;AADF,SAAS,UAAU,EAAE,WAAW,GAAG,MAAM,GAAmB;AACjE,SAAO,6CAAC,SAAI,WAAW,GAAG,aAAa,SAAS,GAAI,GAAG,OAAO;AAChE;;;ACDS,IAAAC,sBAAA;AADF,SAAS,QAAQ,EAAE,WAAW,GAAG,MAAM,GAAiB;AAC7D,SAAO,6CAAC,aAAQ,WAAW,GAAG,WAAW,SAAS,GAAI,GAAG,OAAO;AAClE;AAWO,SAAS,cAAc,EAAE,OAAO,OAAO,aAAa,UAAU,GAAuB;AAC1F,SACE,8CAAC,SAAI,WAAW,GAAG,kBAAkB,SAAS,GAC3C;AAAA,YAAQ,6CAAC,SAAI,WAAU,iBAAiB,iBAAM,IAAS;AAAA,IACxD,6CAAC,QAAG,WAAU,iBAAiB,iBAAM;AAAA,IACpC,cAAc,6CAAC,OAAE,WAAU,gBAAgB,uBAAY,IAAO;AAAA,KACjE;AAEJ;","names":["import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ClassValue } from 'clsx';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { HTMLAttributes, ButtonHTMLAttributes, ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Merge class names, resolving conflicting Tailwind utilities (last wins).
|
|
7
|
+
* Mirrors the helper used across the FloPay frontends so consumers can extend
|
|
8
|
+
* any primitive's classes safely.
|
|
9
|
+
*/
|
|
10
|
+
declare function cn(...inputs: ClassValue[]): string;
|
|
11
|
+
|
|
12
|
+
interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
|
|
13
|
+
/** Render a leading status dot. */
|
|
14
|
+
dot?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/** Pill badge, optionally with a leading status dot. */
|
|
17
|
+
declare function Badge({ dot, className, children, ...props }: BadgeProps): react.JSX.Element;
|
|
18
|
+
|
|
19
|
+
type ButtonVariant = 'primary' | 'secondary' | 'outline';
|
|
20
|
+
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
21
|
+
variant?: ButtonVariant;
|
|
22
|
+
}
|
|
23
|
+
declare function Button({ variant, className, type, ...props }: ButtonProps): react.JSX.Element;
|
|
24
|
+
|
|
25
|
+
type CardProps = HTMLAttributes<HTMLDivElement>;
|
|
26
|
+
/** Bordered surface card with hover elevation. */
|
|
27
|
+
declare function Card({ className, ...props }: CardProps): react.JSX.Element;
|
|
28
|
+
|
|
29
|
+
type ContainerProps = HTMLAttributes<HTMLDivElement>;
|
|
30
|
+
/** Centered, max-width content wrapper (var(--max-w)) with horizontal padding. */
|
|
31
|
+
declare function Container({ className, ...props }: ContainerProps): react.JSX.Element;
|
|
32
|
+
|
|
33
|
+
type SectionProps = HTMLAttributes<HTMLElement>;
|
|
34
|
+
/** Vertical page section with max-width and standard padding. */
|
|
35
|
+
declare function Section({ className, ...props }: SectionProps): react.JSX.Element;
|
|
36
|
+
interface SectionHeaderProps {
|
|
37
|
+
/** Small eyebrow label above the title (brand colored). */
|
|
38
|
+
label?: ReactNode;
|
|
39
|
+
title: ReactNode;
|
|
40
|
+
description?: ReactNode;
|
|
41
|
+
className?: string;
|
|
42
|
+
}
|
|
43
|
+
/** Centered section heading: optional label, title, optional description. */
|
|
44
|
+
declare function SectionHeader({ label, title, description, className }: SectionHeaderProps): react.JSX.Element;
|
|
45
|
+
|
|
46
|
+
export { Badge, type BadgeProps, Button, type ButtonProps, type ButtonVariant, Card, type CardProps, Container, type ContainerProps, Section, SectionHeader, type SectionHeaderProps, type SectionProps, cn };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ClassValue } from 'clsx';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { HTMLAttributes, ButtonHTMLAttributes, ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Merge class names, resolving conflicting Tailwind utilities (last wins).
|
|
7
|
+
* Mirrors the helper used across the FloPay frontends so consumers can extend
|
|
8
|
+
* any primitive's classes safely.
|
|
9
|
+
*/
|
|
10
|
+
declare function cn(...inputs: ClassValue[]): string;
|
|
11
|
+
|
|
12
|
+
interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
|
|
13
|
+
/** Render a leading status dot. */
|
|
14
|
+
dot?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/** Pill badge, optionally with a leading status dot. */
|
|
17
|
+
declare function Badge({ dot, className, children, ...props }: BadgeProps): react.JSX.Element;
|
|
18
|
+
|
|
19
|
+
type ButtonVariant = 'primary' | 'secondary' | 'outline';
|
|
20
|
+
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
21
|
+
variant?: ButtonVariant;
|
|
22
|
+
}
|
|
23
|
+
declare function Button({ variant, className, type, ...props }: ButtonProps): react.JSX.Element;
|
|
24
|
+
|
|
25
|
+
type CardProps = HTMLAttributes<HTMLDivElement>;
|
|
26
|
+
/** Bordered surface card with hover elevation. */
|
|
27
|
+
declare function Card({ className, ...props }: CardProps): react.JSX.Element;
|
|
28
|
+
|
|
29
|
+
type ContainerProps = HTMLAttributes<HTMLDivElement>;
|
|
30
|
+
/** Centered, max-width content wrapper (var(--max-w)) with horizontal padding. */
|
|
31
|
+
declare function Container({ className, ...props }: ContainerProps): react.JSX.Element;
|
|
32
|
+
|
|
33
|
+
type SectionProps = HTMLAttributes<HTMLElement>;
|
|
34
|
+
/** Vertical page section with max-width and standard padding. */
|
|
35
|
+
declare function Section({ className, ...props }: SectionProps): react.JSX.Element;
|
|
36
|
+
interface SectionHeaderProps {
|
|
37
|
+
/** Small eyebrow label above the title (brand colored). */
|
|
38
|
+
label?: ReactNode;
|
|
39
|
+
title: ReactNode;
|
|
40
|
+
description?: ReactNode;
|
|
41
|
+
className?: string;
|
|
42
|
+
}
|
|
43
|
+
/** Centered section heading: optional label, title, optional description. */
|
|
44
|
+
declare function SectionHeader({ label, title, description, className }: SectionHeaderProps): react.JSX.Element;
|
|
45
|
+
|
|
46
|
+
export { Badge, type BadgeProps, Button, type ButtonProps, type ButtonVariant, Card, type CardProps, Container, type ContainerProps, Section, SectionHeader, type SectionHeaderProps, type SectionProps, cn };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// src/cn.ts
|
|
2
|
+
import { clsx } from "clsx";
|
|
3
|
+
import { twMerge } from "tailwind-merge";
|
|
4
|
+
function cn(...inputs) {
|
|
5
|
+
return twMerge(clsx(inputs));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// src/components/badge.tsx
|
|
9
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
function Badge({ dot = false, className, children, ...props }) {
|
|
11
|
+
return /* @__PURE__ */ jsxs("span", { className: cn("badge", className), ...props, children: [
|
|
12
|
+
dot ? /* @__PURE__ */ jsx("span", { className: "badge-dot" }) : null,
|
|
13
|
+
children
|
|
14
|
+
] });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/components/button.tsx
|
|
18
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
19
|
+
var variantClass = {
|
|
20
|
+
primary: "btn-primary",
|
|
21
|
+
secondary: "btn-secondary",
|
|
22
|
+
outline: "btn-outline"
|
|
23
|
+
};
|
|
24
|
+
function Button({ variant = "primary", className, type = "button", ...props }) {
|
|
25
|
+
return /* @__PURE__ */ jsx2("button", { type, className: cn(variantClass[variant], className), ...props });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// src/components/card.tsx
|
|
29
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
30
|
+
function Card({ className, ...props }) {
|
|
31
|
+
return /* @__PURE__ */ jsx3("div", { className: cn("card", className), ...props });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/components/container.tsx
|
|
35
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
36
|
+
function Container({ className, ...props }) {
|
|
37
|
+
return /* @__PURE__ */ jsx4("div", { className: cn("container", className), ...props });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/components/section.tsx
|
|
41
|
+
import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
42
|
+
function Section({ className, ...props }) {
|
|
43
|
+
return /* @__PURE__ */ jsx5("section", { className: cn("section", className), ...props });
|
|
44
|
+
}
|
|
45
|
+
function SectionHeader({ label, title, description, className }) {
|
|
46
|
+
return /* @__PURE__ */ jsxs2("div", { className: cn("section-center", className), children: [
|
|
47
|
+
label ? /* @__PURE__ */ jsx5("div", { className: "section-label", children: label }) : null,
|
|
48
|
+
/* @__PURE__ */ jsx5("h2", { className: "section-title", children: title }),
|
|
49
|
+
description ? /* @__PURE__ */ jsx5("p", { className: "section-desc", children: description }) : null
|
|
50
|
+
] });
|
|
51
|
+
}
|
|
52
|
+
export {
|
|
53
|
+
Badge,
|
|
54
|
+
Button,
|
|
55
|
+
Card,
|
|
56
|
+
Container,
|
|
57
|
+
Section,
|
|
58
|
+
SectionHeader,
|
|
59
|
+
cn
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cn.ts","../src/components/badge.tsx","../src/components/button.tsx","../src/components/card.tsx","../src/components/container.tsx","../src/components/section.tsx"],"sourcesContent":["import { type ClassValue, clsx } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Merge class names, resolving conflicting Tailwind utilities (last wins).\n * Mirrors the helper used across the FloPay frontends so consumers can extend\n * any primitive's classes safely.\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","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 leading status dot. */\nexport function Badge({ dot = false, className, children, ...props }: BadgeProps) {\n return (\n <span className={cn('badge', 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';\n\nexport interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: ButtonVariant;\n}\n\nconst variantClass: Record<ButtonVariant, string> = {\n primary: 'btn-primary',\n secondary: 'btn-secondary',\n outline: 'btn-outline',\n};\n\nexport function Button({ variant = 'primary', className, type = 'button', ...props }: ButtonProps) {\n return <button type={type} className={cn(variantClass[variant], className)} {...props} />;\n}\n","import type { HTMLAttributes } from 'react';\nimport { cn } from '../cn';\n\nexport type CardProps = HTMLAttributes<HTMLDivElement>;\n\n/** Bordered surface card with hover elevation. */\nexport function Card({ className, ...props }: CardProps) {\n return <div className={cn('card', className)} {...props} />;\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 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":";AAAA,SAA0B,YAAY;AACtC,SAAS,eAAe;AAOjB,SAAS,MAAM,QAAsB;AAC1C,SAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;;;ACCI,SACS,KADT;AAFG,SAAS,MAAM,EAAE,MAAM,OAAO,WAAW,UAAU,GAAG,MAAM,GAAe;AAChF,SACE,qBAAC,UAAK,WAAW,GAAG,SAAS,SAAS,GAAI,GAAG,OAC1C;AAAA,UAAM,oBAAC,UAAK,WAAU,aAAY,IAAK;AAAA,IACvC;AAAA,KACH;AAEJ;;;ACAS,gBAAAA,YAAA;AAPT,IAAM,eAA8C;AAAA,EAClD,SAAS;AAAA,EACT,WAAW;AAAA,EACX,SAAS;AACX;AAEO,SAAS,OAAO,EAAE,UAAU,WAAW,WAAW,OAAO,UAAU,GAAG,MAAM,GAAgB;AACjG,SAAO,gBAAAA,KAAC,YAAO,MAAY,WAAW,GAAG,aAAa,OAAO,GAAG,SAAS,GAAI,GAAG,OAAO;AACzF;;;ACVS,gBAAAC,YAAA;AADF,SAAS,KAAK,EAAE,WAAW,GAAG,MAAM,GAAc;AACvD,SAAO,gBAAAA,KAAC,SAAI,WAAW,GAAG,QAAQ,SAAS,GAAI,GAAG,OAAO;AAC3D;;;ACDS,gBAAAC,YAAA;AADF,SAAS,UAAU,EAAE,WAAW,GAAG,MAAM,GAAmB;AACjE,SAAO,gBAAAA,KAAC,SAAI,WAAW,GAAG,aAAa,SAAS,GAAI,GAAG,OAAO;AAChE;;;ACDS,gBAAAC,MAcL,QAAAC,aAdK;AADF,SAAS,QAAQ,EAAE,WAAW,GAAG,MAAM,GAAiB;AAC7D,SAAO,gBAAAD,KAAC,aAAQ,WAAW,GAAG,WAAW,SAAS,GAAI,GAAG,OAAO;AAClE;AAWO,SAAS,cAAc,EAAE,OAAO,OAAO,aAAa,UAAU,GAAuB;AAC1F,SACE,gBAAAC,MAAC,SAAI,WAAW,GAAG,kBAAkB,SAAS,GAC3C;AAAA,YAAQ,gBAAAD,KAAC,SAAI,WAAU,iBAAiB,iBAAM,IAAS;AAAA,IACxD,gBAAAA,KAAC,QAAG,WAAU,iBAAiB,iBAAM;AAAA,IACpC,cAAc,gBAAAA,KAAC,OAAE,WAAU,gBAAgB,uBAAY,IAAO;AAAA,KACjE;AAEJ;","names":["jsx","jsx","jsx","jsx","jsxs"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flopay/ui",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"packageManager": "pnpm@9.15.0",
|
|
6
|
+
"description": "FloPay shared design system — design tokens, a Tailwind v4 preset, and React primitives shared across landing, demo, docs, and dashboard.",
|
|
7
|
+
"license": "UNLICENSED",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"registry": "https://registry.npmjs.org/",
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/TeamFloPay/ui.git"
|
|
15
|
+
},
|
|
16
|
+
"sideEffects": [
|
|
17
|
+
"**/*.css"
|
|
18
|
+
],
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"import": "./dist/index.mjs",
|
|
23
|
+
"require": "./dist/index.cjs"
|
|
24
|
+
},
|
|
25
|
+
"./styles": "./styles/index.css",
|
|
26
|
+
"./styles/index.css": "./styles/index.css",
|
|
27
|
+
"./styles/tokens.css": "./styles/tokens.css",
|
|
28
|
+
"./styles/reset.css": "./styles/reset.css",
|
|
29
|
+
"./styles/components.css": "./styles/components.css",
|
|
30
|
+
"./styles/preset.css": "./styles/preset.css",
|
|
31
|
+
"./package.json": "./package.json"
|
|
32
|
+
},
|
|
33
|
+
"main": "./dist/index.cjs",
|
|
34
|
+
"module": "./dist/index.mjs",
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"styles"
|
|
39
|
+
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsup",
|
|
42
|
+
"dev": "tsup --watch",
|
|
43
|
+
"typecheck": "tsc --noEmit",
|
|
44
|
+
"biome": "biome check",
|
|
45
|
+
"biome:fix": "biome check --write",
|
|
46
|
+
"clean": "rm -rf dist",
|
|
47
|
+
"prepublishOnly": "node scripts/check-publish-registry.cjs"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"react": "^19",
|
|
51
|
+
"react-dom": "^19"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"clsx": "^2.1.1",
|
|
55
|
+
"tailwind-merge": "^3.6.0"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@biomejs/biome": "^2.4.15",
|
|
59
|
+
"@types/react": "^19.2.15",
|
|
60
|
+
"@types/react-dom": "^19.2.3",
|
|
61
|
+
"react": "^19.2.6",
|
|
62
|
+
"react-dom": "^19.2.6",
|
|
63
|
+
"tsup": "^8.3.0",
|
|
64
|
+
"typescript": "^6.0.3"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/* @flopay/ui — shared component classes.
|
|
2
|
+
*
|
|
3
|
+
* Plain-CSS apps import this alongside tokens (or just `@flopay/ui/styles`).
|
|
4
|
+
* Tailwind apps generally compose utilities from preset.css instead and need
|
|
5
|
+
* not import this file. These classes back the React primitives exported from
|
|
6
|
+
* the package root and depend on tokens.css custom properties. */
|
|
7
|
+
|
|
8
|
+
/* ── Layout ── */
|
|
9
|
+
.container {
|
|
10
|
+
width: 100%;
|
|
11
|
+
max-width: var(--max-w);
|
|
12
|
+
margin: 0 auto;
|
|
13
|
+
padding: 0 24px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.section {
|
|
17
|
+
padding: 120px 24px;
|
|
18
|
+
max-width: var(--max-w);
|
|
19
|
+
margin: 0 auto;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.section-center {
|
|
23
|
+
text-align: center;
|
|
24
|
+
margin-bottom: 64px;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.section-label {
|
|
28
|
+
font-size: 13px;
|
|
29
|
+
font-weight: 600;
|
|
30
|
+
color: var(--brand);
|
|
31
|
+
letter-spacing: 0.02em;
|
|
32
|
+
margin-bottom: 12px;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.section-title {
|
|
36
|
+
font-size: clamp(28px, 3.5vw, 40px);
|
|
37
|
+
font-weight: 650;
|
|
38
|
+
letter-spacing: -0.03em;
|
|
39
|
+
line-height: 1.15;
|
|
40
|
+
margin-bottom: 16px;
|
|
41
|
+
color: var(--text);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.section-desc {
|
|
45
|
+
font-size: 17px;
|
|
46
|
+
color: var(--text-secondary);
|
|
47
|
+
max-width: 540px;
|
|
48
|
+
margin: 0 auto;
|
|
49
|
+
line-height: 1.6;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* ── Buttons ── */
|
|
53
|
+
.btn-primary {
|
|
54
|
+
display: inline-flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
gap: 6px;
|
|
57
|
+
padding: 12px 24px;
|
|
58
|
+
background: var(--accent);
|
|
59
|
+
color: #fff;
|
|
60
|
+
font-family: var(--font);
|
|
61
|
+
font-weight: 500;
|
|
62
|
+
font-size: 15px;
|
|
63
|
+
border-radius: var(--radius);
|
|
64
|
+
border: none;
|
|
65
|
+
cursor: pointer;
|
|
66
|
+
transition: background 0.15s, transform 0.1s;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.btn-primary:hover {
|
|
70
|
+
background: var(--accent-hover);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.btn-primary:active {
|
|
74
|
+
transform: scale(0.98);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.btn-secondary {
|
|
78
|
+
display: inline-flex;
|
|
79
|
+
align-items: center;
|
|
80
|
+
gap: 6px;
|
|
81
|
+
padding: 12px 24px;
|
|
82
|
+
background: var(--bg);
|
|
83
|
+
color: var(--text);
|
|
84
|
+
font-family: var(--font);
|
|
85
|
+
font-weight: 500;
|
|
86
|
+
font-size: 15px;
|
|
87
|
+
border-radius: var(--radius);
|
|
88
|
+
border: 1px solid var(--border);
|
|
89
|
+
cursor: pointer;
|
|
90
|
+
transition: background 0.15s, border-color 0.15s;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.btn-secondary:hover {
|
|
94
|
+
background: var(--bg-subtle);
|
|
95
|
+
border-color: var(--border-hover);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.btn-outline {
|
|
99
|
+
display: inline-flex;
|
|
100
|
+
align-items: center;
|
|
101
|
+
gap: 6px;
|
|
102
|
+
padding: 12px 24px;
|
|
103
|
+
background: transparent;
|
|
104
|
+
color: var(--text);
|
|
105
|
+
font-family: var(--font);
|
|
106
|
+
font-weight: 500;
|
|
107
|
+
font-size: 15px;
|
|
108
|
+
border-radius: var(--radius);
|
|
109
|
+
border: 1px solid var(--border);
|
|
110
|
+
cursor: pointer;
|
|
111
|
+
transition: background 0.15s, border-color 0.15s;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.btn-outline:hover {
|
|
115
|
+
background: var(--bg-subtle);
|
|
116
|
+
border-color: var(--border-hover);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* ── Badge ── */
|
|
120
|
+
.badge {
|
|
121
|
+
display: inline-flex;
|
|
122
|
+
align-items: center;
|
|
123
|
+
gap: 6px;
|
|
124
|
+
padding: 5px 14px;
|
|
125
|
+
border-radius: 100px;
|
|
126
|
+
border: 1px solid var(--border);
|
|
127
|
+
background: var(--bg);
|
|
128
|
+
font-size: 13px;
|
|
129
|
+
color: var(--text-secondary);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.badge-dot {
|
|
133
|
+
width: 7px;
|
|
134
|
+
height: 7px;
|
|
135
|
+
border-radius: 50%;
|
|
136
|
+
background: var(--success);
|
|
137
|
+
flex-shrink: 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* ── Card ── */
|
|
141
|
+
.card {
|
|
142
|
+
background: var(--bg);
|
|
143
|
+
border: 1px solid var(--border);
|
|
144
|
+
border-radius: var(--radius-lg);
|
|
145
|
+
padding: 28px;
|
|
146
|
+
transition: box-shadow 0.2s, border-color 0.2s;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.card:hover {
|
|
150
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* ── Nav (shared chrome) ── */
|
|
154
|
+
.nav {
|
|
155
|
+
position: fixed;
|
|
156
|
+
top: 0;
|
|
157
|
+
left: 0;
|
|
158
|
+
right: 0;
|
|
159
|
+
z-index: 100;
|
|
160
|
+
height: var(--nav-h);
|
|
161
|
+
background: var(--bg);
|
|
162
|
+
border-bottom: 1px solid var(--border);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.nav-inner {
|
|
166
|
+
max-width: var(--max-w);
|
|
167
|
+
height: 100%;
|
|
168
|
+
margin: 0 auto;
|
|
169
|
+
padding: 0 24px;
|
|
170
|
+
display: flex;
|
|
171
|
+
align-items: center;
|
|
172
|
+
justify-content: space-between;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.nav-logo {
|
|
176
|
+
font-weight: 600;
|
|
177
|
+
font-size: 1.1rem;
|
|
178
|
+
letter-spacing: -0.02em;
|
|
179
|
+
display: flex;
|
|
180
|
+
align-items: center;
|
|
181
|
+
gap: 0.5rem;
|
|
182
|
+
color: var(--text);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.nav-logo-mark {
|
|
186
|
+
width: 24px;
|
|
187
|
+
height: 24px;
|
|
188
|
+
background: var(--brand);
|
|
189
|
+
border-radius: 6px;
|
|
190
|
+
display: flex;
|
|
191
|
+
align-items: center;
|
|
192
|
+
justify-content: center;
|
|
193
|
+
overflow: hidden;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.nav-logo-mark img {
|
|
197
|
+
width: 100%;
|
|
198
|
+
height: 100%;
|
|
199
|
+
object-fit: cover;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.nav-links {
|
|
203
|
+
display: flex;
|
|
204
|
+
align-items: center;
|
|
205
|
+
gap: 8px;
|
|
206
|
+
list-style: none;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.nav-links a {
|
|
210
|
+
font-size: 14px;
|
|
211
|
+
color: var(--text-secondary);
|
|
212
|
+
padding: 6px 12px;
|
|
213
|
+
border-radius: 6px;
|
|
214
|
+
transition: color 0.15s, background 0.15s;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.nav-links a:hover {
|
|
218
|
+
color: var(--text);
|
|
219
|
+
background: var(--bg-subtle);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.nav-cta {
|
|
223
|
+
background: var(--accent) !important;
|
|
224
|
+
color: #fff !important;
|
|
225
|
+
font-weight: 500;
|
|
226
|
+
padding: 6px 16px !important;
|
|
227
|
+
border-radius: 6px;
|
|
228
|
+
font-size: 14px;
|
|
229
|
+
transition: background 0.15s !important;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.nav-cta:hover {
|
|
233
|
+
background: var(--accent-hover) !important;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/* ── Footer (shared chrome) ── */
|
|
237
|
+
.footer {
|
|
238
|
+
border-top: 1px solid var(--border-light);
|
|
239
|
+
padding: 56px 24px 40px;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.footer-inner {
|
|
243
|
+
max-width: var(--max-w);
|
|
244
|
+
margin: 0 auto;
|
|
245
|
+
display: grid;
|
|
246
|
+
grid-template-columns: 2fr 1fr 1fr 1fr;
|
|
247
|
+
gap: 48px;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.footer-brand p {
|
|
251
|
+
font-size: 14px;
|
|
252
|
+
color: var(--text-tertiary);
|
|
253
|
+
max-width: 260px;
|
|
254
|
+
margin-top: 12px;
|
|
255
|
+
line-height: 1.5;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.footer-column h4 {
|
|
259
|
+
font-size: 13px;
|
|
260
|
+
font-weight: 600;
|
|
261
|
+
color: var(--text);
|
|
262
|
+
margin-bottom: 16px;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.footer-column ul {
|
|
266
|
+
list-style: none;
|
|
267
|
+
display: flex;
|
|
268
|
+
flex-direction: column;
|
|
269
|
+
gap: 10px;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.footer-column a {
|
|
273
|
+
font-size: 14px;
|
|
274
|
+
color: var(--text-tertiary);
|
|
275
|
+
transition: color 0.15s;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.footer-column a:hover {
|
|
279
|
+
color: var(--text);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.footer-bottom {
|
|
283
|
+
max-width: var(--max-w);
|
|
284
|
+
margin: 40px auto 0;
|
|
285
|
+
padding-top: 24px;
|
|
286
|
+
border-top: 1px solid var(--border-light);
|
|
287
|
+
display: flex;
|
|
288
|
+
justify-content: space-between;
|
|
289
|
+
align-items: center;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.footer-bottom p {
|
|
293
|
+
font-size: 13px;
|
|
294
|
+
color: var(--text-tertiary);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.footer-legal {
|
|
298
|
+
display: flex;
|
|
299
|
+
gap: 20px;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.footer-legal a {
|
|
303
|
+
font-size: 13px;
|
|
304
|
+
color: var(--text-tertiary);
|
|
305
|
+
transition: color 0.15s;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.footer-legal a:hover {
|
|
309
|
+
color: var(--text);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
@media (max-width: 1024px) {
|
|
313
|
+
.footer-inner {
|
|
314
|
+
grid-template-columns: 1fr 1fr;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
@media (max-width: 768px) {
|
|
319
|
+
.nav-inner {
|
|
320
|
+
padding: 0 16px;
|
|
321
|
+
}
|
|
322
|
+
.section {
|
|
323
|
+
padding: 80px 16px;
|
|
324
|
+
}
|
|
325
|
+
.footer {
|
|
326
|
+
padding: 40px 16px 32px;
|
|
327
|
+
}
|
|
328
|
+
.footer-inner {
|
|
329
|
+
grid-template-columns: 1fr;
|
|
330
|
+
gap: 32px;
|
|
331
|
+
}
|
|
332
|
+
.footer-bottom {
|
|
333
|
+
flex-direction: column;
|
|
334
|
+
gap: 12px;
|
|
335
|
+
}
|
|
336
|
+
}
|
package/styles/index.css
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/* @flopay/ui — one-stop stylesheet for plain-CSS apps (landing, demo).
|
|
2
|
+
*
|
|
3
|
+
* Usage in app/globals.css:
|
|
4
|
+
* @import "@flopay/ui/styles";
|
|
5
|
+
*
|
|
6
|
+
* Brings in the reset, design tokens, and shared component classes. Apps add
|
|
7
|
+
* their own page-specific CSS after this import. Tailwind apps should import
|
|
8
|
+
* `@flopay/ui/styles/preset.css` instead (see that file). */
|
|
9
|
+
|
|
10
|
+
@import './tokens.css';
|
|
11
|
+
@import './reset.css';
|
|
12
|
+
@import './components.css';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/* @flopay/ui — Tailwind v4 theme preset.
|
|
2
|
+
*
|
|
3
|
+
* Usage in a consumer's global stylesheet (e.g. app/globals.css):
|
|
4
|
+
*
|
|
5
|
+
* @import "tailwindcss";
|
|
6
|
+
* @import "@flopay/ui/styles/preset.css";
|
|
7
|
+
*
|
|
8
|
+
* This exposes the design tokens as Tailwind theme values, so utilities like
|
|
9
|
+
* `bg-bg`, `text-text-secondary`, `bg-brand`, and `font-sans` resolve to the
|
|
10
|
+
* shared tokens. `@theme inline` keeps the generated utilities pointing at the
|
|
11
|
+
* live `var(--token)` references, so the `.dark` overrides in tokens.css apply
|
|
12
|
+
* at runtime without recompiling. */
|
|
13
|
+
|
|
14
|
+
@import './tokens.css';
|
|
15
|
+
|
|
16
|
+
@theme inline {
|
|
17
|
+
/* Surfaces */
|
|
18
|
+
--color-bg: var(--bg);
|
|
19
|
+
--color-bg-subtle: var(--bg-subtle);
|
|
20
|
+
--color-bg-muted: var(--bg-muted);
|
|
21
|
+
--color-bg-card: var(--bg-card);
|
|
22
|
+
--color-bg-elevated: var(--bg-elevated);
|
|
23
|
+
--color-bg-hover: var(--bg-hover);
|
|
24
|
+
|
|
25
|
+
/* Borders */
|
|
26
|
+
--color-border: var(--border);
|
|
27
|
+
--color-border-light: var(--border-light);
|
|
28
|
+
--color-border-hover: var(--border-hover);
|
|
29
|
+
|
|
30
|
+
/* Text */
|
|
31
|
+
--color-text: var(--text);
|
|
32
|
+
--color-text-secondary: var(--text-secondary);
|
|
33
|
+
--color-text-tertiary: var(--text-tertiary);
|
|
34
|
+
|
|
35
|
+
/* Accent */
|
|
36
|
+
--color-accent: var(--accent);
|
|
37
|
+
--color-accent-hover: var(--accent-hover);
|
|
38
|
+
|
|
39
|
+
/* Brand */
|
|
40
|
+
--color-brand: var(--brand);
|
|
41
|
+
--color-brand-light: var(--brand-light);
|
|
42
|
+
--color-brand-secondary: var(--brand-secondary);
|
|
43
|
+
|
|
44
|
+
/* Status */
|
|
45
|
+
--color-success: var(--success);
|
|
46
|
+
--color-danger: var(--danger);
|
|
47
|
+
--color-warning: var(--warning);
|
|
48
|
+
|
|
49
|
+
/* Typography */
|
|
50
|
+
--font-sans: var(--font);
|
|
51
|
+
--font-mono: var(--font-mono);
|
|
52
|
+
}
|
package/styles/reset.css
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/* @flopay/ui — base reset + document defaults.
|
|
2
|
+
* Depends on tokens.css custom properties. */
|
|
3
|
+
|
|
4
|
+
*,
|
|
5
|
+
*::before,
|
|
6
|
+
*::after {
|
|
7
|
+
margin: 0;
|
|
8
|
+
padding: 0;
|
|
9
|
+
box-sizing: border-box;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
html {
|
|
13
|
+
scroll-behavior: smooth;
|
|
14
|
+
-webkit-font-smoothing: antialiased;
|
|
15
|
+
-moz-osx-font-smoothing: grayscale;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
body {
|
|
19
|
+
background: var(--bg);
|
|
20
|
+
color: var(--text);
|
|
21
|
+
font-family: var(--font);
|
|
22
|
+
font-size: 15px;
|
|
23
|
+
line-height: 1.6;
|
|
24
|
+
overflow-x: hidden;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
a {
|
|
28
|
+
color: inherit;
|
|
29
|
+
text-decoration: none;
|
|
30
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/* @flopay/ui — design tokens (single source of truth)
|
|
2
|
+
*
|
|
3
|
+
* Framework-agnostic CSS custom properties. Imported directly by plain-CSS
|
|
4
|
+
* apps (landing, demo) and re-exposed as Tailwind v4 theme values by preset.css
|
|
5
|
+
* (docs, dashboard). Change a value here and every app inherits it.
|
|
6
|
+
*
|
|
7
|
+
* Reconciled superset of the previous landing + demo token sets.
|
|
8
|
+
* Canonical brand is FloPay blue (#1785e0).
|
|
9
|
+
*/
|
|
10
|
+
:root {
|
|
11
|
+
/* Surfaces */
|
|
12
|
+
--bg: #ffffff;
|
|
13
|
+
--bg-subtle: #f7f7f8;
|
|
14
|
+
--bg-muted: #f0f0f2;
|
|
15
|
+
--bg-card: #ffffff;
|
|
16
|
+
--bg-elevated: #f5f5f5;
|
|
17
|
+
--bg-hover: #ebebeb;
|
|
18
|
+
|
|
19
|
+
/* Borders */
|
|
20
|
+
--border: #e5e5e8;
|
|
21
|
+
--border-light: #ededf0;
|
|
22
|
+
--border-hover: #d4d4d4;
|
|
23
|
+
|
|
24
|
+
/* Text */
|
|
25
|
+
--text: #0a0a0b;
|
|
26
|
+
--text-secondary: #52525b;
|
|
27
|
+
--text-tertiary: #a1a1aa;
|
|
28
|
+
|
|
29
|
+
/* Accent — neutral near-black, used for primary actions */
|
|
30
|
+
--accent: #0a0a0b;
|
|
31
|
+
--accent-hover: #27272a;
|
|
32
|
+
|
|
33
|
+
/* Brand */
|
|
34
|
+
--brand: #1785e0;
|
|
35
|
+
--brand-light: #4ba6ec;
|
|
36
|
+
--brand-secondary: #38d299;
|
|
37
|
+
|
|
38
|
+
/* Status */
|
|
39
|
+
--success: #16a34a;
|
|
40
|
+
--danger: #dc2626;
|
|
41
|
+
--warning: #f5a623;
|
|
42
|
+
|
|
43
|
+
/* Radius */
|
|
44
|
+
--radius: 8px;
|
|
45
|
+
--radius-lg: 12px;
|
|
46
|
+
|
|
47
|
+
/* Layout */
|
|
48
|
+
--max-w: 1100px;
|
|
49
|
+
--nav-h: 64px;
|
|
50
|
+
|
|
51
|
+
/* Typography */
|
|
52
|
+
--font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
53
|
+
--font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Dark theme — starting point; refine per app. Activated via a `.dark` class on
|
|
57
|
+
* a root element (matches the next-themes / Fumadocs convention used by docs). */
|
|
58
|
+
.dark {
|
|
59
|
+
--bg: #0a0a0b;
|
|
60
|
+
--bg-subtle: #141416;
|
|
61
|
+
--bg-muted: #1c1c1f;
|
|
62
|
+
--bg-card: #141416;
|
|
63
|
+
--bg-elevated: #1c1c1f;
|
|
64
|
+
--bg-hover: #232327;
|
|
65
|
+
|
|
66
|
+
--border: #27272a;
|
|
67
|
+
--border-light: #1f1f23;
|
|
68
|
+
--border-hover: #3f3f46;
|
|
69
|
+
|
|
70
|
+
--text: #fafafa;
|
|
71
|
+
--text-secondary: #a1a1aa;
|
|
72
|
+
--text-tertiary: #71717a;
|
|
73
|
+
|
|
74
|
+
--accent: #fafafa;
|
|
75
|
+
--accent-hover: #e4e4e7;
|
|
76
|
+
}
|