@finggujadhav/react 0.9.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/button.d.ts +18 -0
- package/dist/button.d.ts.map +1 -0
- package/dist/button.js +46 -0
- package/dist/card.d.ts +10 -0
- package/dist/card.d.ts.map +1 -0
- package/dist/card.js +37 -0
- package/dist/dropdown.d.ts +10 -0
- package/dist/dropdown.d.ts.map +1 -0
- package/dist/dropdown.js +32 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/input.d.ts +7 -0
- package/dist/input.d.ts.map +1 -0
- package/dist/input.js +15 -0
- package/dist/modal.d.ts +10 -0
- package/dist/modal.d.ts.map +1 -0
- package/dist/modal.js +28 -0
- package/dist/provider.d.ts +27 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +54 -0
- package/dist/tabs.d.ts +19 -0
- package/dist/tabs.d.ts.map +1 -0
- package/dist/tabs.js +29 -0
- package/package.json +37 -0
- package/src/button.tsx +72 -0
- package/src/card.tsx +60 -0
- package/src/dropdown.tsx +62 -0
- package/src/index.ts +7 -0
- package/src/input.tsx +23 -0
- package/src/modal.tsx +47 -0
- package/src/provider.tsx +94 -0
- package/src/tabs.tsx +58 -0
package/dist/button.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
3
|
+
variant?: 'primary' | 'secondary' | 'ghost' | 'outline';
|
|
4
|
+
size?: 'sm' | 'md' | 'lg';
|
|
5
|
+
motion?: 'fade' | 'slide-up' | 'scale-in' | 'lift';
|
|
6
|
+
glass?: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Static manifest of all classes used by Button.
|
|
10
|
+
* Used by compiler for deterministic tree-shaking regardless of build order.
|
|
11
|
+
*/
|
|
12
|
+
export declare const __ffClasses_Button: string[];
|
|
13
|
+
/**
|
|
14
|
+
* Finggu Button Component
|
|
15
|
+
* Maps props to deterministic ff-* classes or hashed equivalents.
|
|
16
|
+
*/
|
|
17
|
+
export declare const Button: React.FC<ButtonProps>;
|
|
18
|
+
//# sourceMappingURL=button.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../src/button.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,MAAM,WAAW,WAAY,SAAQ,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC;IAC9E,OAAO,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,OAAO,GAAG,SAAS,CAAC;IACxD,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,CAAC;IACnD,KAAK,CAAC,EAAE,OAAO,CAAC;CACnB;AAsBD;;;GAGG;AACH,eAAO,MAAM,kBAAkB,UAM9B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,CAyBxC,CAAC"}
|
package/dist/button.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useFinggu } from './provider';
|
|
3
|
+
const VARIANTS = {
|
|
4
|
+
primary: 'ff-btn-primary',
|
|
5
|
+
secondary: 'ff-btn-secondary',
|
|
6
|
+
ghost: 'ff-btn-ghost',
|
|
7
|
+
outline: 'ff-btn-outline'
|
|
8
|
+
};
|
|
9
|
+
const SIZES = {
|
|
10
|
+
sm: 'ff-btn-sm',
|
|
11
|
+
md: 'ff-btn-md',
|
|
12
|
+
lg: 'ff-btn-lg'
|
|
13
|
+
};
|
|
14
|
+
const MOTIONS = {
|
|
15
|
+
fade: 'ff-fade-in',
|
|
16
|
+
'slide-up': 'ff-slide-up',
|
|
17
|
+
'scale-in': 'ff-scale-in',
|
|
18
|
+
lift: 'ff-hover-lift'
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Static manifest of all classes used by Button.
|
|
22
|
+
* Used by compiler for deterministic tree-shaking regardless of build order.
|
|
23
|
+
*/
|
|
24
|
+
export const __ffClasses_Button = [
|
|
25
|
+
'ff-btn',
|
|
26
|
+
...Object.values(VARIANTS),
|
|
27
|
+
...Object.values(SIZES),
|
|
28
|
+
...Object.values(MOTIONS),
|
|
29
|
+
'ff-card-glass'
|
|
30
|
+
];
|
|
31
|
+
/**
|
|
32
|
+
* Finggu Button Component
|
|
33
|
+
* Maps props to deterministic ff-* classes or hashed equivalents.
|
|
34
|
+
*/
|
|
35
|
+
export const Button = ({ variant = 'primary', size = 'md', motion, glass, className, children, ...props }) => {
|
|
36
|
+
const { resolveAll } = useFinggu();
|
|
37
|
+
const ffClasses = resolveAll([
|
|
38
|
+
'ff-btn',
|
|
39
|
+
VARIANTS[variant],
|
|
40
|
+
SIZES[size],
|
|
41
|
+
glass && 'ff-card-glass',
|
|
42
|
+
motion && MOTIONS[motion],
|
|
43
|
+
className
|
|
44
|
+
]);
|
|
45
|
+
return (_jsx("button", { className: ffClasses, ...props, children: children }));
|
|
46
|
+
};
|
package/dist/card.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
variant?: 'outline' | 'glass';
|
|
4
|
+
padding?: 'none' | 'sm' | 'md' | 'lg';
|
|
5
|
+
}
|
|
6
|
+
export declare const __ffClasses_Card: string[];
|
|
7
|
+
export declare const Card: React.FC<CardProps>;
|
|
8
|
+
export declare const CardHeader: React.FC<React.HTMLAttributes<HTMLDivElement>>;
|
|
9
|
+
export declare const CardBody: React.FC<React.HTMLAttributes<HTMLDivElement>>;
|
|
10
|
+
//# sourceMappingURL=card.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"card.d.ts","sourceRoot":"","sources":["../src/card.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,MAAM,WAAW,SAAU,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IACnE,OAAO,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;CACzC;AAcD,eAAO,MAAM,gBAAgB,UAM5B,CAAC;AAEF,eAAO,MAAM,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,SAAS,CAqBpC,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,CAGrE,CAAC;AAEF,eAAO,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,CAGnE,CAAC"}
|
package/dist/card.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useFinggu } from './provider';
|
|
3
|
+
const VARIANTS = {
|
|
4
|
+
outline: 'ff-card-outline',
|
|
5
|
+
glass: 'ff-card-glass'
|
|
6
|
+
};
|
|
7
|
+
const PADDINGS = {
|
|
8
|
+
none: 'ff-p-0',
|
|
9
|
+
sm: 'ff-p-2',
|
|
10
|
+
md: 'ff-p-4',
|
|
11
|
+
lg: 'ff-p-8'
|
|
12
|
+
};
|
|
13
|
+
export const __ffClasses_Card = [
|
|
14
|
+
'ff-card',
|
|
15
|
+
'ff-card-header',
|
|
16
|
+
'ff-card-body',
|
|
17
|
+
...Object.values(VARIANTS),
|
|
18
|
+
...Object.values(PADDINGS)
|
|
19
|
+
];
|
|
20
|
+
export const Card = ({ variant, padding = 'md', className, children, ...props }) => {
|
|
21
|
+
const { resolveAll } = useFinggu();
|
|
22
|
+
const ffClasses = resolveAll([
|
|
23
|
+
'ff-card',
|
|
24
|
+
variant && VARIANTS[variant],
|
|
25
|
+
PADDINGS[padding],
|
|
26
|
+
className
|
|
27
|
+
]);
|
|
28
|
+
return (_jsx("div", { className: ffClasses, ...props, children: children }));
|
|
29
|
+
};
|
|
30
|
+
export const CardHeader = ({ className, children, ...props }) => {
|
|
31
|
+
const { resolveAll } = useFinggu();
|
|
32
|
+
return _jsx("div", { className: resolveAll(['ff-card-header', className]), ...props, children: children });
|
|
33
|
+
};
|
|
34
|
+
export const CardBody = ({ className, children, ...props }) => {
|
|
35
|
+
const { resolveAll } = useFinggu();
|
|
36
|
+
return _jsx("div", { className: resolveAll(['ff-card-body', className]), ...props, children: children });
|
|
37
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface DropdownProps {
|
|
3
|
+
trigger: React.ReactNode;
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
align?: 'left' | 'right';
|
|
6
|
+
}
|
|
7
|
+
export declare const __ffClasses_Dropdown: string[];
|
|
8
|
+
export declare const Dropdown: React.FC<DropdownProps>;
|
|
9
|
+
export declare const DropdownItem: React.FC<React.HTMLAttributes<HTMLDivElement>>;
|
|
10
|
+
//# sourceMappingURL=dropdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dropdown.d.ts","sourceRoot":"","sources":["../src/dropdown.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAG3D,MAAM,WAAW,aAAa;IAC1B,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAC5B;AAED,eAAO,MAAM,oBAAoB,UAMhC,CAAC;AAEF,eAAO,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,aAAa,CAuC5C,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,CAGvE,CAAC"}
|
package/dist/dropdown.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useRef, useEffect } from 'react';
|
|
3
|
+
import { useFinggu } from './provider';
|
|
4
|
+
export const __ffClasses_Dropdown = [
|
|
5
|
+
'ff-dropdown',
|
|
6
|
+
'ff-dropdown-trigger',
|
|
7
|
+
'ff-dropdown-menu',
|
|
8
|
+
'ff-dropdown-menu-right',
|
|
9
|
+
'ff-dropdown-item'
|
|
10
|
+
];
|
|
11
|
+
export const Dropdown = ({ trigger, children, align = 'left' }) => {
|
|
12
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
13
|
+
const containerRef = useRef(null);
|
|
14
|
+
const { resolveAll } = useFinggu();
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const handleClickOutside = (event) => {
|
|
17
|
+
if (containerRef.current && !containerRef.current.contains(event.target)) {
|
|
18
|
+
setIsOpen(false);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
22
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
23
|
+
}, []);
|
|
24
|
+
return (_jsxs("div", { className: resolveAll(['ff-dropdown']), ref: containerRef, children: [_jsx("button", { type: "button", className: resolveAll(['ff-dropdown-trigger']), onClick: () => setIsOpen(!isOpen), "aria-haspopup": "true", "aria-expanded": isOpen, children: trigger }), _jsx("div", { className: resolveAll([
|
|
25
|
+
'ff-dropdown-menu',
|
|
26
|
+
align === 'right' && 'ff-dropdown-menu-right'
|
|
27
|
+
]), "data-ff-state": isOpen ? 'open' : 'closed', role: "menu", "aria-hidden": !isOpen, children: children })] }));
|
|
28
|
+
};
|
|
29
|
+
export const DropdownItem = ({ className, children, ...props }) => {
|
|
30
|
+
const { resolveAll } = useFinggu();
|
|
31
|
+
return _jsx("div", { className: resolveAll(['ff-dropdown-item', className]), ...props, children: children });
|
|
32
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC"}
|
package/dist/index.js
ADDED
package/dist/input.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
3
|
+
error?: boolean;
|
|
4
|
+
}
|
|
5
|
+
export declare const __ffClasses_Input: string[];
|
|
6
|
+
export declare const Input: React.FC<InputProps>;
|
|
7
|
+
//# sourceMappingURL=input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../src/input.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,MAAM,WAAW,UAAW,SAAQ,KAAK,CAAC,mBAAmB,CAAC,gBAAgB,CAAC;IAC3E,KAAK,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,eAAO,MAAM,iBAAiB,UAG7B,CAAC;AAEF,eAAO,MAAM,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,UAAU,CAUtC,CAAC"}
|
package/dist/input.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useFinggu } from './provider';
|
|
3
|
+
export const __ffClasses_Input = [
|
|
4
|
+
'ff-input',
|
|
5
|
+
'ff-input-error'
|
|
6
|
+
];
|
|
7
|
+
export const Input = ({ error, className, ...props }) => {
|
|
8
|
+
const { resolveAll } = useFinggu();
|
|
9
|
+
const ffClasses = resolveAll([
|
|
10
|
+
'ff-input',
|
|
11
|
+
error && 'ff-input-error',
|
|
12
|
+
className
|
|
13
|
+
]);
|
|
14
|
+
return _jsx("input", { className: ffClasses, ...props });
|
|
15
|
+
};
|
package/dist/modal.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface ModalProps {
|
|
3
|
+
isOpen: boolean;
|
|
4
|
+
onClose: () => void;
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const __ffClasses_Modal: string[];
|
|
9
|
+
export declare const Modal: React.FC<ModalProps>;
|
|
10
|
+
//# sourceMappingURL=modal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modal.d.ts","sourceRoot":"","sources":["../src/modal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAGnD,MAAM,WAAW,UAAU;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,iBAAiB,UAM7B,CAAC;AAEF,eAAO,MAAM,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,UAAU,CA4BtC,CAAC"}
|
package/dist/modal.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { useFinggu } from './provider';
|
|
4
|
+
export const __ffClasses_Modal = [
|
|
5
|
+
'ff-modal',
|
|
6
|
+
'ff-modal-open',
|
|
7
|
+
'ff-modal-closed',
|
|
8
|
+
'ff-modal-overlay',
|
|
9
|
+
'ff-modal-content'
|
|
10
|
+
];
|
|
11
|
+
export const Modal = ({ isOpen, onClose, children, className }) => {
|
|
12
|
+
const { resolveAll } = useFinggu();
|
|
13
|
+
const [mounted, setMounted] = useState(false);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (isOpen) {
|
|
16
|
+
setMounted(true);
|
|
17
|
+
document.body.style.overflow = 'hidden';
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
const timer = setTimeout(() => setMounted(false), 300); // Wait for exit animation
|
|
21
|
+
document.body.style.overflow = '';
|
|
22
|
+
return () => clearTimeout(timer);
|
|
23
|
+
}
|
|
24
|
+
}, [isOpen]);
|
|
25
|
+
if (!mounted && !isOpen)
|
|
26
|
+
return null;
|
|
27
|
+
return (_jsxs("div", { className: resolveAll(['ff-modal', isOpen ? 'ff-modal-open' : 'ff-modal-closed']), "aria-hidden": !isOpen, children: [_jsx("div", { className: "ff-modal-overlay", onClick: onClose }), _jsx("div", { className: resolveAll(['ff-modal-content', className]), children: children })] }));
|
|
28
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { FingguTheme } from '@finggujadhav/js-helper';
|
|
3
|
+
export interface FingguMapping {
|
|
4
|
+
_version?: string;
|
|
5
|
+
[key: string]: string | undefined;
|
|
6
|
+
}
|
|
7
|
+
export interface FingguProviderProps {
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
mapping?: FingguMapping;
|
|
10
|
+
mode?: 'dev' | 'opt' | 'ext';
|
|
11
|
+
version?: string;
|
|
12
|
+
theme?: FingguTheme;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Provides FingguFlux class mapping to the component tree.
|
|
16
|
+
*/
|
|
17
|
+
export declare const FingguProvider: React.FC<FingguProviderProps>;
|
|
18
|
+
/**
|
|
19
|
+
* Hook to resolve FingguFlux classes to their mapped versions.
|
|
20
|
+
*/
|
|
21
|
+
export declare const useFinggu: () => {
|
|
22
|
+
resolve: (className: string) => string;
|
|
23
|
+
resolveAll: (classes: (string | undefined | null | false)[]) => string;
|
|
24
|
+
mode: "dev" | "opt" | "ext";
|
|
25
|
+
theme: FingguTheme | undefined;
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAgE,MAAM,OAAO,CAAC;AACrF,OAAO,EAAY,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,aAAa;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CACrC;AAcD,MAAM,WAAW,mBAAmB;IAChC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,IAAI,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,WAAW,CAAC;CACvB;AAED;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CA+BxD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,SAAS;yBAGU,MAAM,KAAG,MAAM;0BAed,CAAC,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,KAAK,CAAC,EAAE,KAAG,MAAM;;;CAQ9E,CAAC"}
|
package/dist/provider.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useMemo, useEffect, useRef } from 'react';
|
|
3
|
+
import { setTheme } from '@finggujadhav/js-helper';
|
|
4
|
+
const FingguContext = createContext({
|
|
5
|
+
mapping: null,
|
|
6
|
+
mode: 'dev'
|
|
7
|
+
});
|
|
8
|
+
/**
|
|
9
|
+
* Provides FingguFlux class mapping to the component tree.
|
|
10
|
+
*/
|
|
11
|
+
export const FingguProvider = ({ children, mapping = null, mode = 'dev', version, theme = 'system' }) => {
|
|
12
|
+
const containerRef = useRef(null);
|
|
13
|
+
useMemo(() => {
|
|
14
|
+
if (mapping && version && mapping._version && mapping._version !== version) {
|
|
15
|
+
console.warn(`[FingguFlux] Version Mismatch: mapping.json (${mapping._version}) does not match expected CSS version (${version})`);
|
|
16
|
+
}
|
|
17
|
+
}, [mapping, version]);
|
|
18
|
+
// Apply theme to container for isolation
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (containerRef.current) {
|
|
21
|
+
setTheme(theme, containerRef.current);
|
|
22
|
+
}
|
|
23
|
+
}, [theme]);
|
|
24
|
+
const value = useMemo(() => ({ mapping, mode, version, theme }), [mapping, mode, version, theme]);
|
|
25
|
+
return (_jsx(FingguContext.Provider, { value: value, children: _jsx("div", { ref: containerRef, style: { display: 'contents' }, children: children }) }));
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Hook to resolve FingguFlux classes to their mapped versions.
|
|
29
|
+
*/
|
|
30
|
+
export const useFinggu = () => {
|
|
31
|
+
const { mapping, mode, theme } = useContext(FingguContext);
|
|
32
|
+
const resolve = (className) => {
|
|
33
|
+
if (mode === 'dev' || !mapping)
|
|
34
|
+
return className;
|
|
35
|
+
const mapped = mapping[className];
|
|
36
|
+
if (mode === 'ext' && !mapped && className.startsWith('ff-')) {
|
|
37
|
+
const errorMsg = `[FingguFlux] Critical: Class '${className}' not found in mapping.json in Extreme mode. This will cause broken styles in production.`;
|
|
38
|
+
if (process.env.NODE_ENV === 'development') {
|
|
39
|
+
throw new Error(errorMsg);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
console.error(errorMsg);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return mapped || className;
|
|
46
|
+
};
|
|
47
|
+
const resolveAll = (classes) => {
|
|
48
|
+
return classes
|
|
49
|
+
.filter(Boolean)
|
|
50
|
+
.map(c => resolve(c))
|
|
51
|
+
.join(' ');
|
|
52
|
+
};
|
|
53
|
+
return { resolve, resolveAll, mode, theme };
|
|
54
|
+
};
|
package/dist/tabs.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface TabsProps {
|
|
3
|
+
defaultValue: string;
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
}
|
|
6
|
+
export declare const __ffClasses_Tabs: string[];
|
|
7
|
+
export declare const Tabs: React.FC<TabsProps>;
|
|
8
|
+
export declare const TabList: React.FC<{
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
}>;
|
|
11
|
+
export declare const TabTrigger: React.FC<{
|
|
12
|
+
value: string;
|
|
13
|
+
children: React.ReactNode;
|
|
14
|
+
}>;
|
|
15
|
+
export declare const TabContent: React.FC<{
|
|
16
|
+
value: string;
|
|
17
|
+
children: React.ReactNode;
|
|
18
|
+
}>;
|
|
19
|
+
//# sourceMappingURL=tabs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["../src/tabs.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAUxC,MAAM,WAAW,SAAS;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC7B;AAED,eAAO,MAAM,gBAAgB,UAM5B,CAAC;AAEF,eAAO,MAAM,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,SAAS,CAOpC,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,CAE3D,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,CAe7E,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,CAI7E,CAAC"}
|
package/dist/tabs.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
import { useFinggu } from './provider';
|
|
4
|
+
const TabsContext = React.createContext(null);
|
|
5
|
+
export const __ffClasses_Tabs = [
|
|
6
|
+
'ff-tabs',
|
|
7
|
+
'ff-tab-list',
|
|
8
|
+
'ff-tab',
|
|
9
|
+
'ff-tab-active',
|
|
10
|
+
'ff-tab-content'
|
|
11
|
+
];
|
|
12
|
+
export const Tabs = ({ defaultValue, children }) => {
|
|
13
|
+
const [activeTab, setActiveTab] = useState(defaultValue);
|
|
14
|
+
return (_jsx(TabsContext.Provider, { value: { activeTab, setActiveTab }, children: _jsx("div", { className: "ff-tabs", children: children }) }));
|
|
15
|
+
};
|
|
16
|
+
export const TabList = ({ children }) => (_jsx("div", { className: "ff-tab-list", role: "tablist", children: children }));
|
|
17
|
+
export const TabTrigger = ({ value, children }) => {
|
|
18
|
+
const context = React.useContext(TabsContext);
|
|
19
|
+
const { resolveAll } = useFinggu();
|
|
20
|
+
if (!context)
|
|
21
|
+
return null;
|
|
22
|
+
return (_jsx("button", { role: "tab", "aria-selected": context.activeTab === value, className: resolveAll(['ff-tab', context.activeTab === value && 'ff-tab-active']), onClick: () => context.setActiveTab(value), children: children }));
|
|
23
|
+
};
|
|
24
|
+
export const TabContent = ({ value, children }) => {
|
|
25
|
+
const context = React.useContext(TabsContext);
|
|
26
|
+
if (!context || context.activeTab !== value)
|
|
27
|
+
return null;
|
|
28
|
+
return _jsx("div", { className: "ff-tab-content", children: children });
|
|
29
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@finggujadhav/react",
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "FingguFlux React Adapter - Zero-runtime, high-performance headless UI components.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"src",
|
|
21
|
+
"!src/**/*.test.*",
|
|
22
|
+
"!src/**/__tests__"
|
|
23
|
+
],
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@finggujadhav/core": "0.9.0",
|
|
26
|
+
"@finggujadhav/js-helper": "0.9.0"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"react": ">=16.8.0",
|
|
30
|
+
"react-dom": ">=16.8.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/react": "^18.0.0",
|
|
34
|
+
"@types/react-dom": "^18.0.0",
|
|
35
|
+
"typescript": "^5.0.0"
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/button.tsx
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useFinggu } from './provider';
|
|
3
|
+
|
|
4
|
+
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
5
|
+
variant?: 'primary' | 'secondary' | 'ghost' | 'outline';
|
|
6
|
+
size?: 'sm' | 'md' | 'lg';
|
|
7
|
+
motion?: 'fade' | 'slide-up' | 'scale-in' | 'lift';
|
|
8
|
+
glass?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const VARIANTS = {
|
|
12
|
+
primary: 'ff-btn-primary',
|
|
13
|
+
secondary: 'ff-btn-secondary',
|
|
14
|
+
ghost: 'ff-btn-ghost',
|
|
15
|
+
outline: 'ff-btn-outline'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const SIZES = {
|
|
19
|
+
sm: 'ff-btn-sm',
|
|
20
|
+
md: 'ff-btn-md',
|
|
21
|
+
lg: 'ff-btn-lg'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const MOTIONS = {
|
|
25
|
+
fade: 'ff-fade-in',
|
|
26
|
+
'slide-up': 'ff-slide-up',
|
|
27
|
+
'scale-in': 'ff-scale-in',
|
|
28
|
+
lift: 'ff-hover-lift'
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Static manifest of all classes used by Button.
|
|
33
|
+
* Used by compiler for deterministic tree-shaking regardless of build order.
|
|
34
|
+
*/
|
|
35
|
+
export const __ffClasses_Button = [
|
|
36
|
+
'ff-btn',
|
|
37
|
+
...Object.values(VARIANTS),
|
|
38
|
+
...Object.values(SIZES),
|
|
39
|
+
...Object.values(MOTIONS),
|
|
40
|
+
'ff-card-glass'
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Finggu Button Component
|
|
45
|
+
* Maps props to deterministic ff-* classes or hashed equivalents.
|
|
46
|
+
*/
|
|
47
|
+
export const Button: React.FC<ButtonProps> = ({
|
|
48
|
+
variant = 'primary',
|
|
49
|
+
size = 'md',
|
|
50
|
+
motion,
|
|
51
|
+
glass,
|
|
52
|
+
className,
|
|
53
|
+
children,
|
|
54
|
+
...props
|
|
55
|
+
}) => {
|
|
56
|
+
const { resolveAll } = useFinggu();
|
|
57
|
+
|
|
58
|
+
const ffClasses = resolveAll([
|
|
59
|
+
'ff-btn',
|
|
60
|
+
VARIANTS[variant],
|
|
61
|
+
SIZES[size],
|
|
62
|
+
glass && 'ff-card-glass',
|
|
63
|
+
motion && MOTIONS[motion],
|
|
64
|
+
className
|
|
65
|
+
]);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<button className={ffClasses} {...props}>
|
|
69
|
+
{children}
|
|
70
|
+
</button>
|
|
71
|
+
);
|
|
72
|
+
};
|
package/src/card.tsx
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useFinggu } from './provider';
|
|
3
|
+
|
|
4
|
+
export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
variant?: 'outline' | 'glass';
|
|
6
|
+
padding?: 'none' | 'sm' | 'md' | 'lg';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const VARIANTS = {
|
|
10
|
+
outline: 'ff-card-outline',
|
|
11
|
+
glass: 'ff-card-glass'
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const PADDINGS = {
|
|
15
|
+
none: 'ff-p-0',
|
|
16
|
+
sm: 'ff-p-2',
|
|
17
|
+
md: 'ff-p-4',
|
|
18
|
+
lg: 'ff-p-8'
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const __ffClasses_Card = [
|
|
22
|
+
'ff-card',
|
|
23
|
+
'ff-card-header',
|
|
24
|
+
'ff-card-body',
|
|
25
|
+
...Object.values(VARIANTS),
|
|
26
|
+
...Object.values(PADDINGS)
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
export const Card: React.FC<CardProps> = ({
|
|
30
|
+
variant,
|
|
31
|
+
padding = 'md',
|
|
32
|
+
className,
|
|
33
|
+
children,
|
|
34
|
+
...props
|
|
35
|
+
}) => {
|
|
36
|
+
const { resolveAll } = useFinggu();
|
|
37
|
+
|
|
38
|
+
const ffClasses = resolveAll([
|
|
39
|
+
'ff-card',
|
|
40
|
+
variant && VARIANTS[variant],
|
|
41
|
+
PADDINGS[padding],
|
|
42
|
+
className
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div className={ffClasses} {...props}>
|
|
47
|
+
{children}
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const CardHeader: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ className, children, ...props }) => {
|
|
53
|
+
const { resolveAll } = useFinggu();
|
|
54
|
+
return <div className={resolveAll(['ff-card-header', className])} {...props}>{children}</div>;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const CardBody: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ className, children, ...props }) => {
|
|
58
|
+
const { resolveAll } = useFinggu();
|
|
59
|
+
return <div className={resolveAll(['ff-card-body', className])} {...props}>{children}</div>;
|
|
60
|
+
};
|
package/src/dropdown.tsx
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect } from 'react';
|
|
2
|
+
import { useFinggu } from './provider';
|
|
3
|
+
|
|
4
|
+
export interface DropdownProps {
|
|
5
|
+
trigger: React.ReactNode;
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
align?: 'left' | 'right';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const __ffClasses_Dropdown = [
|
|
11
|
+
'ff-dropdown',
|
|
12
|
+
'ff-dropdown-trigger',
|
|
13
|
+
'ff-dropdown-menu',
|
|
14
|
+
'ff-dropdown-menu-right',
|
|
15
|
+
'ff-dropdown-item'
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export const Dropdown: React.FC<DropdownProps> = ({ trigger, children, align = 'left' }) => {
|
|
19
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
20
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
21
|
+
const { resolveAll } = useFinggu();
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
25
|
+
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
|
|
26
|
+
setIsOpen(false);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
30
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className={resolveAll(['ff-dropdown'])} ref={containerRef}>
|
|
35
|
+
<button
|
|
36
|
+
type="button"
|
|
37
|
+
className={resolveAll(['ff-dropdown-trigger'])}
|
|
38
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
39
|
+
aria-haspopup="true"
|
|
40
|
+
aria-expanded={isOpen}
|
|
41
|
+
>
|
|
42
|
+
{trigger}
|
|
43
|
+
</button>
|
|
44
|
+
<div
|
|
45
|
+
className={resolveAll([
|
|
46
|
+
'ff-dropdown-menu',
|
|
47
|
+
align === 'right' && 'ff-dropdown-menu-right'
|
|
48
|
+
])}
|
|
49
|
+
data-ff-state={isOpen ? 'open' : 'closed'}
|
|
50
|
+
role="menu"
|
|
51
|
+
aria-hidden={!isOpen}
|
|
52
|
+
>
|
|
53
|
+
{children}
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const DropdownItem: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ className, children, ...props }) => {
|
|
60
|
+
const { resolveAll } = useFinggu();
|
|
61
|
+
return <div className={resolveAll(['ff-dropdown-item', className])} {...props}>{children}</div>;
|
|
62
|
+
};
|
package/src/index.ts
ADDED
package/src/input.tsx
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useFinggu } from './provider';
|
|
3
|
+
|
|
4
|
+
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
5
|
+
error?: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const __ffClasses_Input = [
|
|
9
|
+
'ff-input',
|
|
10
|
+
'ff-input-error'
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
export const Input: React.FC<InputProps> = ({ error, className, ...props }) => {
|
|
14
|
+
const { resolveAll } = useFinggu();
|
|
15
|
+
|
|
16
|
+
const ffClasses = resolveAll([
|
|
17
|
+
'ff-input',
|
|
18
|
+
error && 'ff-input-error',
|
|
19
|
+
className
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
return <input className={ffClasses} {...props} />;
|
|
23
|
+
};
|
package/src/modal.tsx
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { useFinggu } from './provider';
|
|
3
|
+
|
|
4
|
+
export interface ModalProps {
|
|
5
|
+
isOpen: boolean;
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const __ffClasses_Modal = [
|
|
12
|
+
'ff-modal',
|
|
13
|
+
'ff-modal-open',
|
|
14
|
+
'ff-modal-closed',
|
|
15
|
+
'ff-modal-overlay',
|
|
16
|
+
'ff-modal-content'
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
export const Modal: React.FC<ModalProps> = ({ isOpen, onClose, children, className }) => {
|
|
20
|
+
const { resolveAll } = useFinggu();
|
|
21
|
+
const [mounted, setMounted] = useState(false);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (isOpen) {
|
|
25
|
+
setMounted(true);
|
|
26
|
+
document.body.style.overflow = 'hidden';
|
|
27
|
+
} else {
|
|
28
|
+
const timer = setTimeout(() => setMounted(false), 300); // Wait for exit animation
|
|
29
|
+
document.body.style.overflow = '';
|
|
30
|
+
return () => clearTimeout(timer);
|
|
31
|
+
}
|
|
32
|
+
}, [isOpen]);
|
|
33
|
+
|
|
34
|
+
if (!mounted && !isOpen) return null;
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div
|
|
38
|
+
className={resolveAll(['ff-modal', isOpen ? 'ff-modal-open' : 'ff-modal-closed'])}
|
|
39
|
+
aria-hidden={!isOpen}
|
|
40
|
+
>
|
|
41
|
+
<div className="ff-modal-overlay" onClick={onClose} />
|
|
42
|
+
<div className={resolveAll(['ff-modal-content', className])}>
|
|
43
|
+
{children}
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
};
|
package/src/provider.tsx
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import React, { createContext, useContext, useMemo, useEffect, useRef } from 'react';
|
|
2
|
+
import { setTheme, FingguTheme } from '@finggujadhav/js-helper';
|
|
3
|
+
|
|
4
|
+
export interface FingguMapping {
|
|
5
|
+
_version?: string;
|
|
6
|
+
[key: string]: string | undefined;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface FingguContextValue {
|
|
10
|
+
mapping: FingguMapping | null;
|
|
11
|
+
mode: 'dev' | 'opt' | 'ext';
|
|
12
|
+
version?: string;
|
|
13
|
+
theme?: FingguTheme;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const FingguContext = createContext<FingguContextValue>({
|
|
17
|
+
mapping: null,
|
|
18
|
+
mode: 'dev'
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export interface FingguProviderProps {
|
|
22
|
+
children: React.ReactNode;
|
|
23
|
+
mapping?: FingguMapping;
|
|
24
|
+
mode?: 'dev' | 'opt' | 'ext';
|
|
25
|
+
version?: string; // Target CSS version to validate against
|
|
26
|
+
theme?: FingguTheme;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Provides FingguFlux class mapping to the component tree.
|
|
31
|
+
*/
|
|
32
|
+
export const FingguProvider: React.FC<FingguProviderProps> = ({
|
|
33
|
+
children,
|
|
34
|
+
mapping = null,
|
|
35
|
+
mode = 'dev',
|
|
36
|
+
version,
|
|
37
|
+
theme = 'system'
|
|
38
|
+
}) => {
|
|
39
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
40
|
+
|
|
41
|
+
useMemo(() => {
|
|
42
|
+
if (mapping && version && mapping._version && mapping._version !== version) {
|
|
43
|
+
console.warn(`[FingguFlux] Version Mismatch: mapping.json (${mapping._version}) does not match expected CSS version (${version})`);
|
|
44
|
+
}
|
|
45
|
+
}, [mapping, version]);
|
|
46
|
+
|
|
47
|
+
// Apply theme to container for isolation
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (containerRef.current) {
|
|
50
|
+
setTheme(theme, containerRef.current);
|
|
51
|
+
}
|
|
52
|
+
}, [theme]);
|
|
53
|
+
|
|
54
|
+
const value = useMemo(() => ({ mapping, mode, version, theme }), [mapping, mode, version, theme]);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<FingguContext.Provider value={value}>
|
|
58
|
+
<div ref={containerRef} style={{ display: 'contents' }}>
|
|
59
|
+
{children}
|
|
60
|
+
</div>
|
|
61
|
+
</FingguContext.Provider>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Hook to resolve FingguFlux classes to their mapped versions.
|
|
67
|
+
*/
|
|
68
|
+
export const useFinggu = () => {
|
|
69
|
+
const { mapping, mode, theme } = useContext(FingguContext);
|
|
70
|
+
|
|
71
|
+
const resolve = (className: string): string => {
|
|
72
|
+
if (mode === 'dev' || !mapping) return className;
|
|
73
|
+
|
|
74
|
+
const mapped = mapping[className];
|
|
75
|
+
if (mode === 'ext' && !mapped && className.startsWith('ff-')) {
|
|
76
|
+
const errorMsg = `[FingguFlux] Critical: Class '${className}' not found in mapping.json in Extreme mode. This will cause broken styles in production.`;
|
|
77
|
+
if (process.env.NODE_ENV === 'development') {
|
|
78
|
+
throw new Error(errorMsg);
|
|
79
|
+
} else {
|
|
80
|
+
console.error(errorMsg);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return mapped || className;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const resolveAll = (classes: (string | undefined | null | false)[]): string => {
|
|
87
|
+
return classes
|
|
88
|
+
.filter(Boolean)
|
|
89
|
+
.map(c => resolve(c as string))
|
|
90
|
+
.join(' ');
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return { resolve, resolveAll, mode, theme };
|
|
94
|
+
};
|
package/src/tabs.tsx
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { useFinggu } from './provider';
|
|
3
|
+
|
|
4
|
+
interface TabsContextValue {
|
|
5
|
+
activeTab: string;
|
|
6
|
+
setActiveTab: (id: string) => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const TabsContext = React.createContext<TabsContextValue | null>(null);
|
|
10
|
+
|
|
11
|
+
export interface TabsProps {
|
|
12
|
+
defaultValue: string;
|
|
13
|
+
children: React.ReactNode;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const __ffClasses_Tabs = [
|
|
17
|
+
'ff-tabs',
|
|
18
|
+
'ff-tab-list',
|
|
19
|
+
'ff-tab',
|
|
20
|
+
'ff-tab-active',
|
|
21
|
+
'ff-tab-content'
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export const Tabs: React.FC<TabsProps> = ({ defaultValue, children }) => {
|
|
25
|
+
const [activeTab, setActiveTab] = useState(defaultValue);
|
|
26
|
+
return (
|
|
27
|
+
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
|
|
28
|
+
<div className="ff-tabs">{children}</div>
|
|
29
|
+
</TabsContext.Provider>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const TabList: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
|
34
|
+
<div className="ff-tab-list" role="tablist">{children}</div>
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
export const TabTrigger: React.FC<{ value: string; children: React.ReactNode }> = ({ value, children }) => {
|
|
38
|
+
const context = React.useContext(TabsContext);
|
|
39
|
+
const { resolveAll } = useFinggu();
|
|
40
|
+
if (!context) return null;
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<button
|
|
44
|
+
role="tab"
|
|
45
|
+
aria-selected={context.activeTab === value}
|
|
46
|
+
className={resolveAll(['ff-tab', context.activeTab === value && 'ff-tab-active'])}
|
|
47
|
+
onClick={() => context.setActiveTab(value)}
|
|
48
|
+
>
|
|
49
|
+
{children}
|
|
50
|
+
</button>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const TabContent: React.FC<{ value: string; children: React.ReactNode }> = ({ value, children }) => {
|
|
55
|
+
const context = React.useContext(TabsContext);
|
|
56
|
+
if (!context || context.activeTab !== value) return null;
|
|
57
|
+
return <div className="ff-tab-content">{children}</div>;
|
|
58
|
+
};
|