+export type StylableComponent> =
+ | StyledComponent
| ComponentType
| ForwardRefExoticComponent
- | ReactComponentWithRef
- | ElementType
+ | ReactComponentWithRef
+ // | ElementType
| (new (_props: P) => any);
-
-export type UnionToIntersection = (
- U extends ConfigVariants ? (_k: U) => string : never
-) extends (_k: infer I) => string
- ? I
- : never;
-
-export type PropsFromExtends<
- E extends StylesFunctionUndefined = undefined
-> = E extends StylesFunction ? P : {};
diff --git a/packages/loader/CHANGELOG.md b/packages/loader/CHANGELOG.md
new file mode 100644
index 00000000..75287813
--- /dev/null
+++ b/packages/loader/CHANGELOG.md
@@ -0,0 +1,47 @@
+# @crossed/loader
+
+## 1.0.0-beta.5
+
+### Patch Changes
+
+- 63171a9: remove Theme plugin for add theme in core, delete withStyle, and createStyles return directly hooks
+- Updated dependencies [63171a9]
+ - @crossed/styled@0.14.0-beta.5
+
+## 1.0.0-beta.4
+
+### Patch Changes
+
+- Updated dependencies
+ - @crossed/styled@0.14.0-beta.4
+
+## 1.0.0-beta.3
+
+### Patch Changes
+
+- 7a7a589: some fix after first usage in external project
+- Updated dependencies [7a7a589]
+ - @crossed/styled@0.14.0-beta.3
+
+## 1.0.0-beta.2
+
+### Patch Changes
+
+- @crossed/styled@0.14.0-beta.2
+
+## 1.0.0-beta.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @crossed/styled@0.14.0-beta.1
+
+## 1.0.0-beta.0
+
+### Patch Changes
+
+- Updated dependencies [d9f9372]
+- Updated dependencies [31e0003]
+- Updated dependencies [c7e44dc]
+- Updated dependencies [8917db4]
+ - @crossed/styled@0.14.0-beta.0
diff --git a/packages/loader/package.json b/packages/loader/package.json
new file mode 100644
index 00000000..7676ebe8
--- /dev/null
+++ b/packages/loader/package.json
@@ -0,0 +1,42 @@
+{
+ "name": "@crossed/loader",
+ "description": "",
+ "version": "1.0.0-beta.5",
+ "main": "lib/commonjs/index",
+ "types": "lib/typescript/index.d.ts",
+ "module": "lib/module/index",
+ "source": "src/index",
+ "typings": "lib/typescript/index.d.ts",
+ "scripts": {
+ "clean": "rm -rf lib",
+ "watch": "crossed-build --watch",
+ "build": "crossed-build",
+ "test": "crossed-test",
+ "type-check": "tsc --noEmit"
+ },
+ "devDependencies": {
+ "@crossed/build": "*",
+ "@crossed/styled": "0.14.0-beta.5",
+ "@crossed/test": "0.11.0",
+ "@types/escodegen": "^0.0.10",
+ "@types/esprima": "^4.0.6",
+ "@types/estree": "^1.0.5",
+ "@types/jest": "^29.5.0",
+ "jest": "^29.5.0",
+ "typescript": "^5.4.2"
+ },
+ "peerDependencies": {
+ "@crossed/styled": "0.14.0-beta.5"
+ },
+ "dependencies": {
+ "@crossed/log": "*",
+ "esbuild": "^0.17.17",
+ "escodegen": "^2.1.0",
+ "esprima": "^4.0.1",
+ "eval-estree-expression": "^1.1.0"
+ },
+ "files": [
+ "lib/",
+ "src/"
+ ]
+}
diff --git a/packages/loader/src/__tests__/getAst.ts b/packages/loader/src/__tests__/getAst.ts
new file mode 100644
index 00000000..54ac6d11
--- /dev/null
+++ b/packages/loader/src/__tests__/getAst.ts
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+import { parseScript as parse } from 'esprima';
+
+export const getAst = (value: string) => {
+ const [ast] = parse(`const Foo = createStyles(${value})`).body;
+ if (
+ ast.type !== 'VariableDeclaration' ||
+ ast.declarations[0].init.type !== 'CallExpression'
+ ) {
+ throw new Error('getAst - wrong value');
+ }
+ return ast.declarations[0].init.arguments[0];
+};
diff --git a/packages/loader/src/__tests__/index.spec.ts b/packages/loader/src/__tests__/index.spec.ts
new file mode 100644
index 00000000..564c8eb7
--- /dev/null
+++ b/packages/loader/src/__tests__/index.spec.ts
@@ -0,0 +1,102 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+import { Loader } from '../index';
+import { getAst } from './getAst';
+import { Registry } from '@crossed/styled/registry';
+import { BasePlugin } from '@crossed/styled/plugins';
+
+Registry.setThemes({ dark: {} })
+ .setThemeName('dark' as unknown as never)
+ .addPlugin(BasePlugin);
+
+jest.mock('esbuild', () => {});
+
+describe('@crossed/loader', () => {
+ test('simple', () => {
+ const loader = new Loader();
+
+ loader.parse(
+ getAst(`{
+ base: {
+ marginTop: 4,
+ width: 50,
+ backgroundColor: "white"
+ }
+ }`)
+ );
+ expect(loader.getCSS()).toEqual(
+ `.dark { }
+.margin-top-\\[4px\\] { margin-top:4px; }
+.width-\\[50px\\] { width:50px; }
+.background-color-\\[white\\] { background-color:white; }`
+ );
+ });
+
+ test('arrow function no explicit return', () => {
+ const loader = new Loader();
+
+ loader.parse(
+ getAst(`() => ({
+ base: {
+ marginTop: 4,
+ width: 50,
+ backgroundColor: "white"
+ }
+ })`)
+ );
+ expect(loader.getCSS()).toEqual(
+ `.dark { }
+.margin-top-\\[4px\\] { margin-top:4px; }
+.width-\\[50px\\] { width:50px; }
+.background-color-\\[white\\] { background-color:white; }`
+ );
+ });
+ test('arrow function explicit return', () => {
+ const loader = new Loader();
+
+ loader.parse(
+ getAst(`() => {
+ return {
+ base: {
+ marginTop: 4,
+ width: 50,
+ backgroundColor: "white"
+ }
+ }
+ }`)
+ );
+ expect(loader.getCSS()).toEqual(
+ `.dark { }
+.margin-top-\\[4px\\] { margin-top:4px; }
+.width-\\[50px\\] { width:50px; }
+.background-color-\\[white\\] { background-color:white; }`
+ );
+ });
+
+ test('Named function explicit return', () => {
+ const loader = new Loader();
+
+ loader.parse(
+ getAst(`function Bar() {
+ return {
+ base: {
+ marginTop: 4,
+ width: 50,
+ backgroundColor: "white"
+ }
+ }
+ }`)
+ );
+ expect(loader.getCSS()).toEqual(
+ `.dark { }
+.margin-top-\\[4px\\] { margin-top:4px; }
+.width-\\[50px\\] { width:50px; }
+.background-color-\\[white\\] { background-color:white; }`
+ );
+ });
+});
diff --git a/packages/loader/src/__tests__/media-query.spec.ts b/packages/loader/src/__tests__/media-query.spec.ts
new file mode 100644
index 00000000..0f02faf6
--- /dev/null
+++ b/packages/loader/src/__tests__/media-query.spec.ts
@@ -0,0 +1,79 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+import { Registry } from '@crossed/styled/registry';
+import { Loader } from '../index';
+import { getAst } from './getAst';
+import { MediaQueriesPlugin, BasePlugin } from '@crossed/styled/plugins';
+
+Registry.setThemes({ dark: {} })
+ .setThemeName('dark' as unknown as never)
+ .addPlugin(BasePlugin)
+ .addPlugin(
+ MediaQueriesPlugin({
+ sm: 500,
+ md: 700,
+ })
+ );
+
+jest.mock('esbuild', () => {});
+
+describe('media-query', () => {
+ test('only min', () => {
+ const loader = new Loader();
+
+ loader.parse(
+ getAst(
+ `()=>({
+ base: {
+ marginTop: 4,
+ width: 50,
+ },
+ media: {
+ md: {
+ backgroundColor: "red"
+ }
+ }
+ })`
+ )
+ );
+ expect(loader.getCSS()).toEqual(
+ `.dark { }
+.margin-top-\\[4px\\] { margin-top:4px; }
+.width-\\[50px\\] { width:50px; }
+@media (min-width: 700px) { .md\\:background-color-\\[red\\] { background-color:red; } }`
+ );
+ });
+
+ test('sm/md', () => {
+ const loader = new Loader();
+
+ loader.parse(
+ getAst(
+ `()=>({
+ base: {
+ marginTop: 4,
+ width: 50,
+ backgroundColor: "black"
+ },
+ media: {
+ sm : { backgroundColor: "green" },
+ md: { backgroundColor: "red" }
+ }
+ })`
+ )
+ );
+ expect(loader.getCSS()).toEqual(
+ `.dark { }
+.margin-top-\\[4px\\] { margin-top:4px; }
+.width-\\[50px\\] { width:50px; }
+.background-color-\\[black\\] { background-color:black; }
+@media (min-width: 500px) { .sm\\:background-color-\\[green\\] { background-color:green; } }
+@media (min-width: 700px) { .md\\:background-color-\\[red\\] { background-color:red; } }`
+ );
+ });
+});
diff --git a/packages/loader/src/__tests__/pseudoClass.spec.ts b/packages/loader/src/__tests__/pseudoClass.spec.ts
new file mode 100644
index 00000000..9ba514bd
--- /dev/null
+++ b/packages/loader/src/__tests__/pseudoClass.spec.ts
@@ -0,0 +1,82 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+import { Registry } from '@crossed/styled/registry';
+import { Loader } from '../index';
+import { getAst } from './getAst';
+import { PseudoClassPlugin, BasePlugin } from '@crossed/styled/plugins';
+
+Registry.setThemes({ dark: {} })
+ .setThemeName('dark' as unknown as never)
+ .addPlugin(BasePlugin)
+ .addPlugin(PseudoClassPlugin);
+
+jest.mock('esbuild', () => {});
+
+describe('pseudoClass', () => {
+ test('hover', () => {
+ const loader = new Loader();
+ loader.parse(
+ getAst(`()=>({
+ base: {
+ backgroundColor: "white"
+ },
+ ':hover': {
+ backgroundColor: "black"
+ }
+ })`)
+ );
+ expect(loader.getCSS()).toEqual(
+ `.dark { }
+.background-color-\\[white\\] { background-color:white; }
+.hover\\:background-color-\\[black\\]:hover { background-color:black; }
+.background-color-\\[black\\] { background-color:black; }`
+ );
+ });
+
+ test('focus', () => {
+ const loader = new Loader();
+
+ loader.parse(
+ getAst(`{
+ base: {
+ backgroundColor: "white"
+ },
+ ':focus': {
+ backgroundColor: "black"
+ }
+ }`)
+ );
+ expect(loader.getCSS()).toEqual(
+ `.dark { }
+.background-color-\\[white\\] { background-color:white; }
+.focus\\:background-color-\\[black\\]:focus { background-color:black; }
+.background-color-\\[black\\] { background-color:black; }`
+ );
+ });
+
+ test('active', () => {
+ const loader = new Loader();
+
+ loader.parse(
+ getAst(`{
+ base: {
+ backgroundColor: "white"
+ },
+ ':active': {
+ backgroundColor: "black"
+ }
+ }`)
+ );
+ expect(loader.getCSS()).toEqual(
+ `.dark { }
+.background-color-\\[white\\] { background-color:white; }
+.active\\:background-color-\\[black\\]:active { background-color:black; }
+.background-color-\\[black\\] { background-color:black; }`
+ );
+ });
+});
diff --git a/packages/loader/src/__tests__/variants.spec.ts b/packages/loader/src/__tests__/variants.spec.ts
new file mode 100644
index 00000000..c8b9a7f3
--- /dev/null
+++ b/packages/loader/src/__tests__/variants.spec.ts
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+import { Registry } from '@crossed/styled/registry';
+import { Loader } from '../index';
+import { getAst } from './getAst';
+import { VariantsPlugin, BasePlugin } from '@crossed/styled/plugins';
+
+Registry.setThemes({ dark: {} })
+ .setThemeName('dark' as unknown as never)
+ .addPlugin(BasePlugin)
+ .addPlugin(VariantsPlugin);
+
+jest.mock('esbuild', () => {});
+
+describe('variants', () => {
+ test('variant width media query', () => {});
+ const loader = new Loader();
+ loader.parse(
+ getAst(
+ `()=>({
+ base: {
+ marginTop: 4,
+ width: 50,
+ },
+ variants: {
+ color: {
+ white: { base:{ color: "white", background: "white" } },
+ black: { base: { color: "black", background: "black" } }
+ },
+ size: {
+ sm: { base:{ fontSize: 11 } },
+ md: { base: { fontSize: 14 } }
+ }
+ }
+ })`
+ )
+ );
+ expect(loader.getCSS()).toEqual(
+ `.dark { }
+.margin-top-\\[4px\\] { margin-top:4px; }
+.width-\\[50px\\] { width:50px; }
+.color-\\[white\\] { color:white; }
+.background-\\[white\\] { background:white; }
+.color-\\[black\\] { color:black; }
+.background-\\[black\\] { background:black; }
+.font-size-\\[11px\\] { font-size:11px; }
+.font-size-\\[14px\\] { font-size:14px; }`
+ );
+});
diff --git a/packages/loader/src/index.ts b/packages/loader/src/index.ts
new file mode 100644
index 00000000..a8a5aeed
--- /dev/null
+++ b/packages/loader/src/index.ts
@@ -0,0 +1,235 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+import path from 'path';
+import {
+ ArrowFunctionExpression,
+ // CallExpression,
+ Expression,
+ FunctionExpression,
+ // Identifier,
+ // Literal,
+ ObjectExpression,
+ // Property,
+ // SequenceExpression,
+ SpreadElement,
+} from 'estree';
+// import { parseScript as parse } from 'esprima';
+import escodegen from 'escodegen';
+import { createLogger, apiLog } from '@crossed/log';
+import { Registry, parse } from '@crossed/styled/registry';
+import { convertKeyToCss } from '@crossed/styled/plugins';
+import type { CrossedstyleValues } from '@crossed/styled';
+import * as esbuild from 'esbuild';
+
+type Style = Record;
+
+export class Loader {
+ private logger: ReturnType;
+
+ private fileCache: Set = new Set();
+
+ constructor({
+ level = 'info',
+ configPath,
+ }: {
+ level?: string;
+ configPath?: string;
+ } = {}) {
+ this.logger = createLogger({ label: 'CrossedLoader', level });
+ this.logger.debug(
+ apiLog({
+ events: ['create_instance_success'],
+ }).message
+ );
+
+ if (configPath) {
+ try {
+ esbuild.buildSync({
+ bundle: true,
+ entryPoints: [path.resolve(process.cwd(), configPath)],
+ packages: 'external',
+ outfile: path.resolve(process.cwd(), './lib/style.config.js'),
+ target: 'node20',
+ });
+ } catch (e) {
+ this.logger.error(e.toString());
+ }
+
+ try {
+ require(path.resolve(process.cwd(), './lib/style.config'));
+ } catch (e) {
+ this.logger.error(e.toString());
+ }
+ }
+ }
+
+ styleToString = (style: Style) => {
+ return Object.keys(style).reduce((acc, key) => {
+ const value = style[key];
+ if (key === 'marginHorizontal') {
+ return `${acc}${convertKeyToCss(
+ 'marginLeft'
+ )}:${value};${convertKeyToCss('marginRight')}:${value};`;
+ } else if (key === 'marginVertical') {
+ return `${acc}${convertKeyToCss(
+ 'marginTop'
+ )}:${value};${convertKeyToCss('marginBottom')}:${value};`;
+ } else if (key === 'paddingHorizontal') {
+ return `${acc}${convertKeyToCss(
+ 'paddingLeft'
+ )}:${value};${convertKeyToCss('paddingRight')}:${value};`;
+ } else if (key === 'paddingVertical') {
+ return `${acc}${convertKeyToCss(
+ 'paddingTop'
+ )}:${value};${convertKeyToCss('paddingBottom')}:${value};`;
+ }
+ return `${acc}${convertKeyToCss(key)}:${value};`;
+ }, '');
+ };
+
+ getCSS() {
+ return Array.from(this.fileCache.values()).join('\n');
+ }
+
+ addClassname = (obj: {
+ suffix?: string;
+ prefix?: string;
+ wrapper?: (_str: string) => string;
+ body: Record;
+ }) => {
+ Object.entries(obj.body).forEach(([key, value]) => {
+ // transform { backgroundColor: "blue" } => background-color: blue;
+
+ const styleParsed =
+ typeof value === 'string' ? value : this.styleToString(value);
+
+ // escape some character in css
+ const css = `${obj.prefix ?? '.'}${key.replace(
+ /[#:\[\]\(\)%,]/g,
+ '\\$&'
+ )}${obj.suffix || ''} { ${styleParsed} }`;
+
+ // add css in cahce file
+ this.fileCache.add(obj.wrapper ? obj.wrapper(css) : css);
+ });
+ };
+
+ parse(ast: Expression | SpreadElement, isMulti?: boolean) {
+ const plugins = Registry.getPlugins();
+ const ctx = plugins.reduce((acc, { utils }) => {
+ return { ...acc, ...(utils?.() || undefined) };
+ }, {});
+
+ Object.entries(Registry.getThemes()).forEach(([themeName, theme]) => {
+ this.addClassname({
+ prefix: '.',
+ body: {
+ [`${themeName}`]: parse(theme, undefined, true).values,
+ },
+ });
+ });
+
+ plugins.forEach(({ init }) =>
+ init?.({ addClassname: this.addClassname, isWeb: true, ...ctx })
+ );
+ const _parseObjectExpression = (arg: ObjectExpression) => {
+ if (arg.type === 'ObjectExpression') {
+ const ast = {
+ type: 'Program',
+ body: [
+ {
+ type: 'ExpressionStatement',
+ expression: arg,
+ },
+ ],
+ };
+ const toto = escodegen.generate(ast);
+ let returnEl;
+ try {
+ // eslint-disable-next-line no-eval
+ returnEl = eval(toto);
+ } catch (e) {
+ this.logger.error(
+ apiLog({
+ events: ['eval_style_function_error'],
+ }).message,
+ { message: e.message }
+ );
+ }
+ return returnEl;
+ }
+ };
+
+ const _parseFunctionExpression = (
+ arg: ArrowFunctionExpression | FunctionExpression
+ ) => {
+ if (
+ arg.type === 'ArrowFunctionExpression' ||
+ arg.type === 'FunctionExpression'
+ ) {
+ const ast = {
+ type: 'Program',
+ body: [
+ {
+ type: 'ExpressionStatement',
+ expression: arg,
+ },
+ ],
+ };
+ const toto = escodegen.generate(ast);
+ let returnEl;
+ try {
+ // eslint-disable-next-line no-eval
+ returnEl = eval(toto)(Registry.getTheme());
+ } catch (e) {
+ this.logger.error(
+ apiLog({
+ events: ['eval_style_function_error'],
+ }).message,
+ { message: e.message }
+ );
+ }
+ return returnEl;
+ }
+ };
+
+ let parsing: Record;
+
+ if (ast.type === 'ObjectExpression') {
+ parsing = _parseObjectExpression(ast);
+ } else if (
+ ast.type === 'ArrowFunctionExpression' ||
+ ast.type === 'FunctionExpression'
+ ) {
+ parsing = _parseFunctionExpression(ast);
+ } else {
+ this.logger.warn(
+ apiLog({
+ events: ['ast_type_not_implemented'],
+ }).message,
+ { file: ast.type }
+ );
+ }
+
+ if (parsing) {
+ if (isMulti) {
+ Object.entries(parsing).forEach(([, p]) => {
+ Registry.apply(() => p, {
+ addClassname: this.addClassname,
+ isWeb: true,
+ });
+ });
+ } else {
+ Registry.apply(() => parsing, {
+ addClassname: this.addClassname,
+ isWeb: true,
+ });
+ }
+ }
+ }
+}
diff --git a/packages/loader/tsconfig.json b/packages/loader/tsconfig.json
new file mode 100644
index 00000000..1a2f4ad7
--- /dev/null
+++ b/packages/loader/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "include": ["./src"],
+ "exclude": ["node_modules"],
+ "compilerOptions": {
+ "outDir": "./lib/typescript",
+ "noImplicitAny": true,
+ "module": "es6",
+ "lib": ["es2017"],
+ "target": "es5",
+ "jsx": "react",
+ "allowJs": true,
+ "moduleResolution": "node",
+ "declaration": true,
+ "skipLibCheck": true,
+ "allowSyntheticDefaultImports": true,
+ }
+}
\ No newline at end of file
diff --git a/packages/log/package.json b/packages/log/package.json
new file mode 100644
index 00000000..b3338a06
--- /dev/null
+++ b/packages/log/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "@crossed/log",
+ "description": "",
+ "version": "0.1.0",
+ "main": "lib/commonjs/index",
+ "types": "lib/typescript/index.d.ts",
+ "module": "lib/module/index",
+ "react-native": "src/index",
+ "source": "src/index",
+ "typings": "lib/typescript/index.d.ts",
+ "scripts": {
+ "clean": "rm -rf lib",
+ "watch": "crossed-build --watch",
+ "build": "crossed-build",
+ "type-check": "tsc --noEmit"
+ },
+ "devDependencies": {
+ "@crossed/build": "*",
+ "typescript": "^5.4.2"
+ },
+ "dependencies": {
+ "human-logs": "^0.4.1",
+ "winston": "^3.11.0"
+ },
+ "files": [
+ "lib/",
+ "src/"
+ ]
+}
diff --git a/packages/log/src/index.ts b/packages/log/src/index.ts
new file mode 100644
index 00000000..ef903c63
--- /dev/null
+++ b/packages/log/src/index.ts
@@ -0,0 +1,86 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+import { createHumanLogs } from 'human-logs';
+import winston from 'winston';
+
+export const apiLog = createHumanLogs({
+ events: {
+ eval_style_function_error: 'Cannot eval style with theme',
+ detect_style_function: 'Style creation detection',
+ parser_hook_undefined: 'Parser hook not exist',
+ ast_type_not_implemented: 'Ast type is not implemented',
+ css_output_success: 'css output was written successfully',
+ css_output_error: 'css output error',
+ create_instance_success: 'Plugin bridge created successfully',
+ },
+ explanations: {
+ // api_unreachable: 'because the API cannot be reached.',
+ },
+ solutions: {
+ // check_status_page: {
+ // template: 'You can check the status of our services on our status page.',
+ // params: {},
+ // actions: [
+ // {
+ // text: 'Go to status page',
+ // href: 'https://skosh.dev',
+ // },
+ // ],
+ // },
+ // project_view: {
+ // template: 'View the project.',
+ // params: {},
+ // actions: [
+ // {
+ // text: 'View',
+ // href: 'https://skosh.dev',
+ // },
+ // ],
+ // },
+ },
+});
+
+// export const logger = winston.createLogger({
+// level: 'info',
+// // format: winston.format.combine(
+// // winston.format.colorize(),
+// // winston.format.printf(({ level, message, label }) => {
+// // return `[${label}] ${level}: ${message}`;
+// // })
+// // ),
+// transports: [new winston.transports.Console()],
+// });
+
+export const createLogger = ({
+ label,
+ level,
+}: {
+ label: string;
+ level?: string;
+}) => {
+ return winston.createLogger({
+ level: level ?? 'info',
+ format: winston.format.combine(
+ winston.format.label({ label }),
+ winston.format.colorize(),
+ winston.format.printf(({ level, message, label, file }) => {
+ return `${level}: [${label}] ${message} ${file || ''}`;
+ })
+ ),
+ transports: [new winston.transports.Console()],
+ });
+ // return logger.child({
+ // format: winston.format.combine(
+ // winston.format.label({ label }),
+ // winston.format.colorize(),
+ // winston.format.printf(({ level, message, label }) => {
+ // return `[${label}] ${level}: ${message}`;
+ // })
+ // ),
+ // });
+};
diff --git a/packages/log/tsconfig.json b/packages/log/tsconfig.json
new file mode 100644
index 00000000..894aad2b
--- /dev/null
+++ b/packages/log/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "include": ["./src"],
+ "exclude": ["node_modules"],
+ "compilerOptions": {
+ "outDir": "./lib/typescript",
+ "noImplicitAny": true,
+ "module": "es6",
+ "lib": ["es2017"],
+ "target": "es5",
+ "jsx": "react",
+ "allowJs": true,
+ "moduleResolution": "node",
+ "declaration": true,
+ "skipLibCheck": true,
+ "allowSyntheticDefaultImports": true
+ }
+}
\ No newline at end of file
diff --git a/packages/next-adapter/CHANGELOG.md b/packages/next-adapter/CHANGELOG.md
index 0551e47d..1c261227 100644
--- a/packages/next-adapter/CHANGELOG.md
+++ b/packages/next-adapter/CHANGELOG.md
@@ -1,5 +1,42 @@
# @crossed/next-adapter
+## 0.5.2-beta.5
+
+### Patch Changes
+
+- @crossed/webpack@2.0.0-beta.5
+
+## 0.5.2-beta.4
+
+### Patch Changes
+
+- @crossed/webpack@2.0.0-beta.4
+
+## 0.5.2-beta.3
+
+### Patch Changes
+
+- Updated dependencies [7a7a589]
+ - @crossed/webpack@2.0.0-beta.3
+
+## 0.5.2-beta.2
+
+### Patch Changes
+
+- @crossed/webpack@2.0.0-beta.2
+
+## 0.5.2-beta.1
+
+### Patch Changes
+
+- @crossed/webpack@2.0.0-beta.1
+
+## 0.5.2-beta.0
+
+### Patch Changes
+
+- @crossed/webpack@2.0.0-beta.0
+
## 0.5.1
### Patch Changes
diff --git a/packages/next-adapter/package.json b/packages/next-adapter/package.json
index efa4765e..4e24b968 100644
--- a/packages/next-adapter/package.json
+++ b/packages/next-adapter/package.json
@@ -1,8 +1,10 @@
{
- "version": "0.5.1",
+ "version": "0.5.2-beta.5",
"license": "MIT",
- "main": "dist/index.js",
- "typings": "dist/index.d.ts",
+ "main": "lib/commonjs/index.js",
+ "module": "lib/modules/index.js",
+ "types": "lib/typescript/index.d.ts",
+ "typings": "lib/typescript/index.d.ts",
"files": [
"dist",
"src"
@@ -11,16 +13,16 @@
"node": ">=10"
},
"scripts": {
- "start": "tsdx watch",
- "build": "tsdx build",
+ "clean": "rm -rf lib",
+ "watch": "crossed-build --watch",
+ "build": "crossed-build",
"lint": "tsdx lint",
- "prepare": "tsdx build",
"size": "size-limit",
"analyze": "size-limit --why",
"type-check": "tsc --noEmit"
},
"peerDependencies": {
- "react": ">=16"
+ "react": "18.2.0"
},
"husky": {
"hooks": {
@@ -28,7 +30,6 @@
}
},
"name": "@crossed/next-adapter",
- "module": "dist/next-adapter.esm.js",
"size-limit": [
{
"path": "dist/next-adapter.cjs.production.min.js",
@@ -40,10 +41,11 @@
}
],
"devDependencies": {
+ "@crossed/build": "*",
"@size-limit/preset-small-lib": "^8.1.2",
"@types/fs-extra": "^9.0.13",
"@types/node": "18.11.10",
- "@types/react": "^18.2.21",
+ "@types/react": "^18.2.64",
"@types/react-dom": "^18.2.7",
"husky": "^8.0.3",
"react": "^18.2.0",
@@ -51,9 +53,10 @@
"size-limit": "^8.1.2",
"tsdx": "^0.14.1",
"tslib": "^2.5.0",
- "typescript": "^5.1.6"
+ "typescript": "^5.4.2"
},
"dependencies": {
+ "@crossed/webpack": "2.0.0-beta.5",
"find-yarn-workspace-root": "^2.0.0",
"fs-extra": "^11.1.0",
"next": "^13.4.19",
diff --git a/packages/next-adapter/src/index.tsx b/packages/next-adapter/src/index.tsx
index 1ad8716a..29f59e31 100644
--- a/packages/next-adapter/src/index.tsx
+++ b/packages/next-adapter/src/index.tsx
@@ -5,4 +5,49 @@
* LICENSE file in the root of this projects source tree.
*/
-export { default as withCrossed } from './withCrossed';
+import StylePlugin, { type StylePluginOptions } from '@crossed/webpack';
+export const withCrossed = (options: StylePluginOptions) => {
+ return (nextConfig: any = {}) => {
+ const updatedNextConfig = {
+ ...nextConfig,
+ transpilePackages: [
+ ...(nextConfig.transpilePackages || []),
+ 'react-native',
+ ],
+ webpack: (config: any, context: any) => {
+ const { isServer } = context;
+ config = nextConfig.webpack
+ ? nextConfig.webpack(config, context)
+ : config;
+
+ config.resolve.extensions = [
+ '.web.js',
+ '.web.ts',
+ '.web.tsx',
+ ...config.resolve.extensions,
+ ];
+
+ config.module.rules.push({
+ test: /\.ttf$/,
+ loader: 'url-loader',
+ });
+
+ config.resolve.alias = {
+ ...(config.resolve.alias || {}),
+ 'react-native$': 'react-native-web',
+ };
+
+ // if (!isServer) {
+ config.plugins = [
+ ...config.plugins,
+ new StylePlugin({ ...options, isServer }),
+ ];
+ // }
+
+ return config;
+ },
+ };
+
+ return updatedNextConfig;
+ };
+};
diff --git a/packages/next-adapter/src/utils.ts b/packages/next-adapter/src/utils.ts
deleted file mode 100644
index aa68d922..00000000
--- a/packages/next-adapter/src/utils.ts
+++ /dev/null
@@ -1,138 +0,0 @@
-/**
- * Copyright (c) Paymium.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root of this projects source tree.
- */
-
-import fs from 'fs-extra';
-import path from 'path';
-
-const isIncludedInDependency = (arr: any[], inputString: string) => {
- return arr.includes(inputString);
-};
-
-function traverseFolder(
- dir: any,
- prefixes: any[] = [],
- deps: Set = new Set()
-) {
- const files = fs.readdirSync(dir);
- files.forEach((file: any) => {
- const filePath = path.join(dir, file);
- const stat = fs.statSync(filePath);
- if (stat.isDirectory()) {
- if (isIncludedInDependency(prefixes, file)) {
- deps.add(file);
- } else {
- traverseFolder(filePath, prefixes, deps);
- }
- }
- });
- return deps;
-}
-
-const getExactDependenciesFromNodeModules = (
- dir: any,
- prefixes: any[] = []
-) => {
- const nodeModulesDirectory = path.join(dir, 'node_modules');
- const dependenciesSet = traverseFolder(nodeModulesDirectory, prefixes);
-
- const dependencyList: any = [];
- dependenciesSet.forEach((dependency) => {
- dependencyList.push(dependency);
- });
-
- return dependencyList;
-};
-
-function startsWithAny(string: any, array: any) {
- for (let i = 0; i < array.length; i++) {
- if (string.startsWith(array[i])) {
- return true;
- }
- }
- return false;
-}
-
-const getDependenciesFromNodeModules = (
- dir: any,
- nodeModulePackages: any = []
-) => {
- const myDependencies = new Map();
-
- const traverse = (directory: any) => {
- const files = fs.readdirSync(directory);
-
- for (const file of files) {
- const filePath = path.join(directory, file);
- const stat = fs.statSync(filePath);
-
- if (stat.isDirectory()) {
- traverse(filePath);
- } else if (stat.isFile() && file === 'package.json') {
- // eslint-disable-next-line @typescript-eslint/no-var-requires
- const packageJson = require(filePath);
-
- if (
- packageJson.name &&
- startsWithAny(packageJson.name, nodeModulePackages)
- ) {
- //TODO: add dependencies if needed
- myDependencies.set(packageJson.name, {});
- }
- }
- }
- };
-
- nodeModulePackages.map((nodeModulePackage: any) => {
- const nodeModulesDirectory = path.join(
- dir,
- 'node_modules',
- nodeModulePackage
- );
-
- if (fs.existsSync(nodeModulesDirectory)) {
- traverse(nodeModulesDirectory);
- }
- });
-
- const dependencyList: any = [];
-
- myDependencies.forEach((packageDependencies, packageName) => {
- dependencyList.push(packageName);
- Object.entries(packageDependencies).forEach(([dependencyName]) => {
- dependencyList.push(dependencyName);
- });
- });
-
- return dependencyList;
-};
-
-const checkIfWorkspace = (currDir: any) => {
- const parentFiles = fs.readdirSync(path.resolve(currDir, '..'));
- const metadata: any = {};
-
- if (parentFiles.includes('package.json')) {
- // eslint-disable-next-line @typescript-eslint/no-var-requires
- const parentPackageJson = require(path.resolve(
- currDir,
- '..',
- 'package.json'
- ));
-
- const workspaces = parentPackageJson.workspaces;
- if (workspaces) {
- metadata.isWorkspace = true;
- metadata.workspaces = workspaces;
- }
- }
- return metadata;
-};
-
-export {
- getDependenciesFromNodeModules,
- checkIfWorkspace,
- getExactDependenciesFromNodeModules,
-};
diff --git a/packages/next-adapter/src/withCrossed.ts b/packages/next-adapter/src/withCrossed.ts
deleted file mode 100644
index 30840fab..00000000
--- a/packages/next-adapter/src/withCrossed.ts
+++ /dev/null
@@ -1,128 +0,0 @@
-/**
- * Copyright (c) Paymium.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root of this projects source tree.
- */
-
-import {
- checkIfWorkspace,
- getDependenciesFromNodeModules,
- getExactDependenciesFromNodeModules,
-} from './utils';
-import findWorkspaceRoot from 'find-yarn-workspace-root';
-import path from 'path';
-
-const crossedDeps = ['@crossed', '@react-native-aria', '@expo', '@legendapp'];
-
-const reactNativeDeps = [
- 'react-native',
- 'react-native-web',
- 'react-native-svg',
- '@iconscout/react-native-unicons',
- 'twrnc',
-];
-
-export default function withCrossed(nextConfig: any = {}) {
- const currDir = process.cwd();
- let rootDependencyList = [];
- try {
- rootDependencyList = getDependenciesFromNodeModules(currDir, crossedDeps);
- } catch (e) {}
-
- let rootExactDependencyList = [];
- try {
- rootExactDependencyList = getExactDependenciesFromNodeModules(
- currDir,
- reactNativeDeps
- );
- } catch (e) {}
-
- const workspaceRoot = findWorkspaceRoot(currDir); // Absolute path or null
- const metaWorkspace = checkIfWorkspace(currDir);
-
- let parentDependencyList = [];
- let parentExactDependencyList = [];
-
- if (metaWorkspace.isWorkspace) {
- try {
- parentDependencyList = getDependenciesFromNodeModules(
- path.resolve(currDir, '..'),
- crossedDeps
- );
- parentExactDependencyList = getExactDependenciesFromNodeModules(
- path.resolve(currDir, '..'),
- reactNativeDeps
- );
- } catch (e) {}
- }
-
- // if (metaWorkspace.isWorkspace) {
- // parentDependencyList = getDependenciesFromNodeModules(
- // path.resolve(currDir, '..'),
- // ['@crossed', '@react-native-aria']
- // );
- // }
-
- // if (workspaceRoot) {
- // parentDependencyList = getDependenciesFromNodeModules(workspaceRoot, [
- // '@crossed',
- // '@react-native-aria',
- // '@legendapp',
- // '@expo/html-elements',
- // 'crossed',
- // ]);
- // }
- if (workspaceRoot) {
- try {
- parentDependencyList = getDependenciesFromNodeModules(
- workspaceRoot,
- crossedDeps
- );
- parentExactDependencyList =
- getExactDependenciesFromNodeModules(workspaceRoot);
- } catch (e) {}
- }
-
- const crossedUITranspileModules = Array.from(
- new Set([
- '@iconscout/react-native-unicons',
- ...rootDependencyList,
- ...parentDependencyList,
- ...rootExactDependencyList,
- ...parentExactDependencyList,
- ...(nextConfig.transpilePackages || []),
- ])
- );
-
- const updatedNextConfig = {
- ...nextConfig,
- transpilePackages: crossedUITranspileModules,
- webpack: (config: any, context: any) => {
- config = nextConfig.webpack
- ? nextConfig.webpack(config, context)
- : config;
-
- config.resolve.alias = {
- ...(config.resolve.alias || {}),
- 'react-native$': 'react-native-web',
- };
-
- config.resolve.extensions = [
- '.web.js',
- '.web.ts',
- '.web.tsx',
- ...config.resolve.extensions,
- ];
-
- config.module.rules.push({
- test: /\.ttf$/,
- loader: 'url-loader',
- });
-
- return config;
- },
- };
-
- return updatedNextConfig;
-}
diff --git a/packages/next-adapter/tsconfig.json b/packages/next-adapter/tsconfig.json
index 2d7419fb..8f55217f 100644
--- a/packages/next-adapter/tsconfig.json
+++ b/packages/next-adapter/tsconfig.json
@@ -29,7 +29,5 @@
"skipLibCheck": true,
// error out if import and file system have a casing mismatch. Recommended by TS
"forceConsistentCasingInFileNames": true,
- // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc`
- "noEmit": true
}
}
diff --git a/packages/primitive/CHANGELOG.md b/packages/primitive/CHANGELOG.md
index ac7d97e8..74706dc4 100644
--- a/packages/primitive/CHANGELOG.md
+++ b/packages/primitive/CHANGELOG.md
@@ -1,5 +1,19 @@
# @crossed/primitive
+## 1.7.1-beta.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @crossed/core@0.8.1-beta.1
+
+## 1.7.1-beta.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @crossed/core@0.8.1-beta.0
+
## 1.7.0
### Minor Changes
diff --git a/packages/primitive/package.json b/packages/primitive/package.json
index 7cb48320..9adc0018 100644
--- a/packages/primitive/package.json
+++ b/packages/primitive/package.json
@@ -1,7 +1,7 @@
{
"name": "@crossed/primitive",
"description": "A universal & performant styling library for React Native, Next.js & React",
- "version": "1.7.0",
+ "version": "1.7.1-beta.1",
"main": "lib/commonjs/index",
"types": "lib/typescript/index.d.ts",
"module": "lib/module/index",
@@ -35,19 +35,18 @@
"@crossed/test": "*",
"@testing-library/jest-dom": "^6.1.3",
"@types/jest": "^29.5.0",
- "@types/react": "^18.2.21",
+ "@types/react": "^18.2.64",
"@types/react-dom": "^18.2.7",
- "@types/react-native": "^0.72.5",
"babel-plugin-transform-remove-console": "^6.9.4",
"jest": "^29.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "react-native": "^0.72.5",
+ "react-native": "^0.73.5",
"react-native-web": "^0.19.10",
"tsconfig": "*"
},
"dependencies": {
- "@crossed/core": "*",
+ "@crossed/core": "0.8.1-beta.1",
"@floating-ui/react": "^0.25.1",
"@gorhom/portal": "^1.0.14",
"@react-native-aria/focus": "^0.2.8",
diff --git a/packages/primitive/src/Button/Button.tsx b/packages/primitive/src/Button/Button.tsx
index 51f32987..13da3544 100644
--- a/packages/primitive/src/Button/Button.tsx
+++ b/packages/primitive/src/Button/Button.tsx
@@ -7,7 +7,7 @@
import { ComponentType, forwardRef, useId } from 'react';
import { Provider } from './context';
-import { RovingFocusGroupItem } from '../utils';
+import { RovingFocusGroupItem } from '../utils/RovingFocus';
import { ButtonGroupCollection } from './contextCollection';
export const createButtonMain = >(
diff --git a/packages/primitive/src/Button/contextCollection.ts b/packages/primitive/src/Button/contextCollection.ts
index c7473836..ddd81621 100644
--- a/packages/primitive/src/Button/contextCollection.ts
+++ b/packages/primitive/src/Button/contextCollection.ts
@@ -5,7 +5,7 @@
* LICENSE file in the root of this projects source tree.
*/
-import { createCollection } from '../utils';
+import { createCollection } from '../utils/Collections';
const GROUP_NAME = 'ButtonGroup';
diff --git a/packages/primitive/src/Input/InputGroup.tsx b/packages/primitive/src/Input/InputGroup.tsx
index 7fcbebfa..bbae8aa6 100644
--- a/packages/primitive/src/Input/InputGroup.tsx
+++ b/packages/primitive/src/Input/InputGroup.tsx
@@ -15,8 +15,8 @@ import {
cloneElement,
useState,
} from 'react';
-import { InputProvider, useInputContext } from './context';
-import { composeEventHandlers, type States } from '@crossed/core';
+import { InputProvider, States, useInputContext } from './context';
+import { composeEventHandlers } from '@crossed/core';
export const createInputGroup = (Styled: ComponentType
) =>
forwardRef((props, ref) => {
diff --git a/packages/primitive/src/Input/context.ts b/packages/primitive/src/Input/context.ts
index 07ef6c0b..082be4ff 100644
--- a/packages/primitive/src/Input/context.ts
+++ b/packages/primitive/src/Input/context.ts
@@ -5,9 +5,15 @@
* LICENSE file in the root of this projects source tree.
*/
-import { States, createScope } from '@crossed/core';
+import { createScope } from '@crossed/core';
import type { MutableRefObject } from 'react';
+export type States = {
+ isActive: boolean;
+ isFocus: boolean;
+ isHover: boolean;
+};
+
export type StyleRef = {
style?: any;
className?: any;
diff --git a/packages/primitive/tsconfig.json b/packages/primitive/tsconfig.json
index 4ee21d8c..8b0ae5a3 100644
--- a/packages/primitive/tsconfig.json
+++ b/packages/primitive/tsconfig.json
@@ -26,8 +26,5 @@
"skipLibCheck": true,
"strict": true,
"target": "esnext",
- "paths": {
- "@crossed/core": ["./node_modules/@crossed/core/src"]
- }
}
}
diff --git a/packages/styled/CHANGELOG.md b/packages/styled/CHANGELOG.md
index 30cb1dbe..54dafc40 100644
--- a/packages/styled/CHANGELOG.md
+++ b/packages/styled/CHANGELOG.md
@@ -1,5 +1,50 @@
# @crossed/styled
+## 0.14.0-beta.5
+
+### Patch Changes
+
+- 63171a9: remove Theme plugin for add theme in core, delete withStyle, and createStyles return directly hooks
+
+## 0.14.0-beta.4
+
+### Patch Changes
+
+- fix typescript error not pass props
+- Updated dependencies
+ - @crossed/core@0.8.1-beta.1
+
+## 0.14.0-beta.3
+
+### Patch Changes
+
+- 7a7a589: some fix after first usage in external project
+
+## 0.14.0-beta.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @crossed/core@0.8.1-beta.0
+
+## 0.14.0-beta.1
+
+### Minor Changes
+
+- add plugin
+
+## 0.14.0-beta.0
+
+### Minor Changes
+
+- 31e0003: feat(styled): add exports in package.json
+
+### Patch Changes
+
+- d9f9372: not pass state props on component styled
+- c7e44dc: wrong export type of unistyles
+- 8917db4: state not apply (hovered, active, focus)
+
## 0.13.0
### Minor Changes
diff --git a/packages/styled/__tests__/Registry.spec.ts b/packages/styled/__tests__/Registry.spec.ts
new file mode 100644
index 00000000..5a69f30c
--- /dev/null
+++ b/packages/styled/__tests__/Registry.spec.ts
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+///
+
+import { RegistryBridge, Registry } from '../src/Registry';
+
+describe('Registry', () => {
+ test('be instance of RegitryBridge', () => {
+ expect(Registry).toBeInstanceOf(RegistryBridge);
+ });
+
+ // test('can set/get theme', () => {
+ // const theme = {};
+ // expect(Registry.getTheme()).toBe(undefined);
+ // expect(Registry.setTheme(theme)).toBe(Registry);
+ // expect(Registry.getTheme()).toBe(theme);
+ // });
+
+ test('can add/get plugins', () => {
+ const plugins = {} as any;
+ expect(Registry.getPlugins()).toEqual([]);
+ expect(Registry.addPlugin(plugins)).toBe(Registry);
+ expect(Registry.addPlugin(plugins)).toBe(Registry);
+ expect(Registry.getPlugins()).toEqual([plugins, plugins]);
+ });
+});
diff --git a/packages/styled/__tests__/Registry.spec.tsx b/packages/styled/__tests__/Registry.spec.tsx
deleted file mode 100644
index 8ac9c2ab..00000000
--- a/packages/styled/__tests__/Registry.spec.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * Copyright (c) Paymium.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root of this projects source tree.
- */
-
-import { render } from '@crossed/test';
-import { Registry } from '../src/Registry';
-
-jest.mock('next/navigation', () => ({
- useServerInsertedHTML: jest.fn((e) => e()),
-}));
-jest.mock('next/document', () => ({
- Main: jest.fn(),
-}));
-jest.mock('react-native', () => ({
- AppRegistry: {
- registerComponent: jest.fn(),
- getApplication: jest.fn(() => ({ getStyleElement: jest.fn() })),
- },
-}));
-
-describe('Registry', () => {
- test('render', () => {
- const { useServerInsertedHTML } = jest.requireMock('next/navigation');
- const { AppRegistry } = jest.requireMock('react-native');
- const getStyleElement = jest.fn();
- AppRegistry.getApplication.mockImplementation(() => ({ getStyleElement }));
- render( );
- expect(useServerInsertedHTML).toBeCalled();
- expect(AppRegistry.registerComponent).toBeCalled();
- expect(AppRegistry.getApplication).toBeCalledWith('Main');
- expect(getStyleElement).toBeCalled();
- });
-});
diff --git a/packages/styled/__tests__/createStyles.spec.ts b/packages/styled/__tests__/createStyles.spec.ts
new file mode 100644
index 00000000..a5743324
--- /dev/null
+++ b/packages/styled/__tests__/createStyles.spec.ts
@@ -0,0 +1,126 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+///
+
+import { createStyles } from '../src/createStyles';
+import { BasePlugin } from '../src/plugins/Base';
+import { Registry } from '../src/Registry';
+
+describe('createStyles', () => {
+ beforeAll(() => {
+ Registry.addPlugin(BasePlugin);
+ });
+ test('should return function', () => {
+ const style = createStyles(() => ({ container: {} }));
+ expect(typeof style).toBe('object');
+ expect(style).toHaveProperty('container');
+ expect(typeof style.container).toBe('object');
+ expect(style.container).toHaveProperty('style');
+ expect(style.container).toHaveProperty('className');
+ expect(style.container).toHaveProperty('rnw');
+ });
+
+ const base = { color: 'white', borderColor: 'red' };
+
+ test('style return', () => {
+ const style = createStyles(() => ({
+ container: { base },
+ }));
+ expect(style.container.style()).toStrictEqual({
+ style: base,
+ });
+
+ expect(
+ style.container.style({
+ style: { color: 'black', backgroundColor: 'white' },
+ })
+ ).toStrictEqual({
+ style: { ...base, color: 'black', backgroundColor: 'white' },
+ });
+ });
+
+ test('className return', () => {
+ const style = createStyles(() => ({
+ container: { base },
+ }));
+ expect(style.container.className()).toStrictEqual({
+ className: 'color-[white] border-color-[red]',
+ });
+ expect(
+ style.container.className({
+ style: { color: 'black', backgroundColor: 'white' },
+ })
+ ).toStrictEqual({
+ className: 'color-[black] border-color-[red] background-color-[white]',
+ });
+ expect(
+ style.container.className({
+ style: { color: 'black', backgroundColor: 'white' },
+ className: 'toto',
+ })
+ ).toStrictEqual({
+ className:
+ 'toto color-[black] border-color-[red] background-color-[white]',
+ });
+ });
+
+ test('rnw return', () => {
+ const style = createStyles(() => ({
+ container: { base },
+ }));
+ expect(style.container.rnw()).toStrictEqual({
+ style: [
+ {
+ '$$css': true,
+ 'color-[white]': 'color-[white]',
+ 'border-color-[red]': 'border-color-[red]',
+ },
+ ],
+ });
+ expect(
+ style.container.rnw({
+ style: { color: 'black', backgroundColor: 'white' },
+ })
+ ).toStrictEqual({
+ style: [
+ {
+ '$$css': true,
+ 'color-[black]': 'color-[black]',
+ 'border-color-[red]': 'border-color-[red]',
+ 'background-color-[white]': 'background-color-[white]',
+ },
+ ],
+ });
+ expect(
+ style.container.rnw({
+ style: { color: 'black', backgroundColor: 'white' },
+ className: 'toto',
+ })
+ ).toStrictEqual({
+ style: [
+ {
+ '$$css': true,
+ 'color-[black]': 'color-[black]',
+ 'border-color-[red]': 'border-color-[red]',
+ 'background-color-[white]': 'background-color-[white]',
+ 'toto': 'toto',
+ },
+ ],
+ });
+ });
+
+ // test('call return function', () => {
+ // const { useStyles } = jest.requireMock('../src/useStyles');
+ // const useStylesImpl = jest.fn(() => ({ titi: 'titi' }));
+ // useStyles.mockImplementation(useStylesImpl);
+ // const styleF = () => ({ bar: 'foo' });
+ // const style = createStyles(styleF)({ toto: 'toto' });
+ // expect(useStylesImpl).toBeCalledWith(styleF, { toto: 'toto' });
+ // expect(style).toEqual({ titi: 'titi' });
+ // });
+});
diff --git a/packages/styled/__tests__/extendsStyles.spec.ts b/packages/styled/__tests__/extendsStyles.spec.ts
new file mode 100644
index 00000000..6063321f
--- /dev/null
+++ b/packages/styled/__tests__/extendsStyles.spec.ts
@@ -0,0 +1,136 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+///
+
+import { createStyles } from '../src/createStyles';
+import { BasePlugin } from '../src/plugins/Base';
+import { Registry } from '../src/Registry';
+
+describe('extends with createStyles', () => {
+ beforeAll(() => {
+ Registry.addPlugin(BasePlugin);
+ });
+
+ const style = createStyles(() => ({
+ container: { base: { color: 'white' } },
+ }));
+
+ test('style extends style', () => {
+ expect(
+ style.container.style({
+ style: { color: 'black' },
+ })
+ ).toStrictEqual({
+ style: { color: 'black' },
+ });
+ });
+
+ test('style extends className', () => {
+ expect(
+ style.container.style({
+ className: 'color',
+ })
+ ).toStrictEqual({
+ style: { color: 'white' },
+ });
+ });
+
+ test('style extends rnw', () => {
+ expect(
+ style.container.style({
+ style: { $$css: true, color: 'color' },
+ })
+ ).toStrictEqual({
+ style: { color: 'white' },
+ });
+ });
+
+ test('className extends style', () => {
+ expect(
+ style.container.className({
+ style: { color: 'black' },
+ })
+ ).toStrictEqual({
+ className: 'color-[black]',
+ });
+ });
+
+ test('className extends rnw', () => {
+ expect(
+ style.container.className({
+ style: { $$css: true, color: 'color' },
+ })
+ ).toStrictEqual({
+ className: 'color color-[white]',
+ });
+ });
+
+ // test('className return', () => {
+ // const style = createStyles(() => ({
+ // container: { base },
+ // }));
+ // expect(style.container.className()).toStrictEqual({
+ // className: 'color-[white] border-color-[red]',
+ // });
+ // expect(
+ // style.container.className({
+ // style: { color: 'black', backgroundColor: 'white' },
+ // })
+ // ).toStrictEqual({
+ // className: 'color-[black] border-color-[red] background-color-[white]',
+ // });
+ // expect(
+ // style.container.className({
+ // style: { color: 'black', backgroundColor: 'white' },
+ // className: 'toto',
+ // })
+ // ).toStrictEqual({
+ // className:
+ // 'toto color-[black] border-color-[red] background-color-[white]',
+ // });
+ // });
+
+ // test('rnw return', () => {
+ // const style = createStyles(() => ({
+ // container: { base },
+ // }));
+ // expect(style.container.rnw()).toStrictEqual({
+ // style: {
+ // '$$css': true,
+ // 'color-[white]': 'color-[white]',
+ // 'border-color-[red]': 'border-color-[red]',
+ // },
+ // });
+ // expect(
+ // style.container.rnw({
+ // style: { color: 'black', backgroundColor: 'white' },
+ // })
+ // ).toStrictEqual({
+ // style: {
+ // '$$css': true,
+ // 'color-[black]': 'color-[black]',
+ // 'border-color-[red]': 'border-color-[red]',
+ // 'background-color-[white]': 'background-color-[white]',
+ // },
+ // });
+ // expect(
+ // style.container.rnw({
+ // style: { color: 'black', backgroundColor: 'white' },
+ // className: 'toto',
+ // })
+ // ).toStrictEqual({
+ // style: {
+ // '$$css': true,
+ // 'color-[black]': 'color-[black]',
+ // 'border-color-[red]': 'border-color-[red]',
+ // 'background-color-[white]': 'background-color-[white]',
+ // 'toto': 'toto',
+ // },
+ // });
+ // });
+});
diff --git a/packages/styled/__tests__/extract.spec.tsx b/packages/styled/__tests__/extract.spec.tsx
deleted file mode 100644
index 327838dd..00000000
--- a/packages/styled/__tests__/extract.spec.tsx
+++ /dev/null
@@ -1,149 +0,0 @@
-import { extract } from '../src/extract';
-
-describe('extract', () => {
- describe('simple', () => {
- test('empty', async () => {
- expect(extract({})).toEqual({
- base: {},
- focus: {},
- hover: {},
- active: {},
- });
- });
-
- test('hover', async () => {
- expect(
- extract({
- 'backgroundColor': 'red',
- 'hover:': { backgroundColor: 'white' },
- })
- ).toEqual({
- base: { backgroundColor: 'red' },
- focus: {},
- hover: { backgroundColor: 'white' },
- active: {},
- });
- });
-
- test('focus', async () => {
- expect(
- extract({
- 'backgroundColor': 'red',
- 'focus:': { backgroundColor: 'white' },
- })
- ).toEqual({
- base: { backgroundColor: 'red' },
- focus: { backgroundColor: 'white' },
- hover: {},
- active: {},
- });
- });
- test('active', async () => {
- expect(
- extract({
- 'backgroundColor': 'red',
- 'active:': { backgroundColor: 'white' },
- })
- ).toEqual({
- base: { backgroundColor: 'red' },
- focus: {},
- hover: {},
- active: { backgroundColor: 'white' },
- });
- });
- });
-
- describe('with variant', () => {
- test('hover', async () => {
- expect(
- extract({
- 'backgroundColor': 'gray',
- 'hover:': { backgroundColor: 'white' },
- 'variants': {
- color: {
- red: {
- 'backgroundColor': 'red',
- 'hover:': { backgroundColor: 'orange' },
- },
- },
- },
- })
- ).toEqual({
- base: {
- backgroundColor: 'gray',
- variants: { color: { red: { backgroundColor: 'red' } } },
- },
- focus: {},
- hover: {
- backgroundColor: 'white',
- variants: { color: { red: { backgroundColor: 'orange' } } },
- },
- active: {},
- });
- });
-
- // test('focus', async () => {
- // expect(
- // extract({
- // 'backgroundColor': 'red',
- // 'focus:': { backgroundColor: 'white' },
- // })
- // ).toEqual({
- // base: { backgroundColor: 'red' },
- // checked: {},
- // readOnly: {},
- // required: {},
- // invalid: {},
- // focus: { backgroundColor: 'white' },
- // focusVisible: {},
- // hover: {},
- // pressed: {},
- // active: {},
- // loading: {},
- // disabled: {},
- // });
- // });
- // test('focusVisible', async () => {
- // expect(
- // extract({
- // 'backgroundColor': 'red',
- // 'focusVisible:': { backgroundColor: 'white' },
- // })
- // ).toEqual({
- // base: { backgroundColor: 'red' },
- // checked: {},
- // readOnly: {},
- // required: {},
- // invalid: {},
- // focus: {},
- // focusVisible: { backgroundColor: 'white' },
- // hover: {},
- // pressed: {},
- // active: {},
- // loading: {},
- // disabled: {},
- // });
- // });
- // test('active', async () => {
- // expect(
- // extract({
- // 'backgroundColor': 'red',
- // 'active:': { backgroundColor: 'white' },
- // })
- // ).toEqual({
- // base: { backgroundColor: 'red' },
- // checked: {},
- // readOnly: {},
- // required: {},
- // invalid: {},
- // focus: {},
- // focusVisible: {},
- // hover: {},
- // pressed: {},
- // active: { backgroundColor: 'white' },
- // loading: {},
- // disabled: {},
- // });
- // });
- });
-});
diff --git a/packages/styled/__tests__/hooks/useActive.spec.ts b/packages/styled/__tests__/hooks/useActive.spec.ts
deleted file mode 100644
index 312b9232..00000000
--- a/packages/styled/__tests__/hooks/useActive.spec.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { Signal, renderHook } from '@crossed/test';
-import { useActive } from '../../src/hooks/useActive';
-import { act } from 'react-dom/test-utils';
-
-jest.mock('@preact/signals-react', () => {
- const t = new Signal(false);
- return {
- useComputed: jest.fn((cb) => new Signal(cb())),
- useSignal: jest.fn(() => {
- return t;
- }),
- };
-});
-
-describe('useActive', () => {
- test('basic', () => {
- const props = {
- onPointerUp: jest.fn(),
- onPointerDown: jest.fn(),
- };
- const { result, rerender } = renderHook(() => useActive(props));
- expect(Object.keys(result.current)).toEqual(['active', 'actions']);
- expect(Object.keys(result.current.actions)).toEqual([
- 'onPointerUp',
- 'onPointerDown',
- ]);
- expect(result.current.active.value).toEqual(false);
-
- act(() => result.current.actions.onPointerDown({}));
- expect(props.onPointerDown).toBeCalled();
- rerender();
- expect(result.current.active.value).toEqual(true);
-
- act(() => result.current.actions.onPointerUp({}));
- expect(props.onPointerUp).toBeCalled();
- rerender();
- expect(result.current.active.value).toEqual(false);
- });
-});
diff --git a/packages/styled/__tests__/hooks/useFocus.spec.ts b/packages/styled/__tests__/hooks/useFocus.spec.ts
deleted file mode 100644
index a929d82f..00000000
--- a/packages/styled/__tests__/hooks/useFocus.spec.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { Signal, renderHook } from '@crossed/test';
-import { useFocus } from '../../src/hooks/useFocus';
-import { act } from 'react-dom/test-utils';
-
-jest.mock('@preact/signals-react', () => {
- const t = new Signal(false);
- return {
- useComputed: jest.fn((cb) => new Signal(cb())),
- useSignal: jest.fn(() => {
- return t;
- }),
- };
-});
-
-describe('useFocus', () => {
- test('basic', () => {
- const props = {
- onFocus: jest.fn(),
- onBlur: jest.fn(),
- };
- const { result, rerender } = renderHook(() => useFocus(props));
- expect(Object.keys(result.current)).toEqual(['focus', 'actions']);
- expect(Object.keys(result.current.actions)).toEqual(['onFocus', 'onBlur']);
- expect(result.current.focus.value).toEqual(false);
-
- act(() => result.current.actions.onFocus({}));
- expect(props.onFocus).toBeCalled();
- rerender();
- expect(result.current.focus.value).toEqual(true);
-
- act(() => result.current.actions.onBlur({}));
- expect(props.onBlur).toBeCalled();
- rerender();
- expect(result.current.focus.value).toEqual(false);
- });
-});
diff --git a/packages/styled/__tests__/hooks/useHover.spec.ts b/packages/styled/__tests__/hooks/useHover.spec.ts
deleted file mode 100644
index c720cfb4..00000000
--- a/packages/styled/__tests__/hooks/useHover.spec.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { Signal, renderHook } from '@crossed/test';
-import { useHover } from '../../src/hooks/useHover';
-import { act } from 'react-dom/test-utils';
-
-jest.mock('@preact/signals-react', () => {
- const t = new Signal(false);
- return {
- useComputed: jest.fn((cb) => new Signal(cb())),
- useSignal: jest.fn(() => {
- return t;
- }),
- };
-});
-
-describe('useHover', () => {
- test('basic', () => {
- const props = {
- onPointerEnter: jest.fn(),
- onPointerLeave: jest.fn(),
- };
- const { result, rerender } = renderHook(() => useHover(props));
- expect(Object.keys(result.current)).toEqual(['hovered', 'actions']);
- expect(Object.keys(result.current.actions)).toEqual([
- 'onPointerEnter',
- 'onPointerLeave',
- ]);
- expect(result.current.hovered.value).toEqual(false);
-
- act(() => result.current.actions.onPointerEnter({}));
- expect(props.onPointerEnter).toBeCalled();
- rerender();
- expect(result.current.hovered.value).toEqual(true);
-
- act(() => result.current.actions.onPointerLeave({}));
- expect(props.onPointerLeave).toBeCalled();
- rerender();
- expect(result.current.hovered.value).toEqual(false);
- });
-});
diff --git a/packages/styled/__tests__/parseStyle.spec.ts b/packages/styled/__tests__/parseStyle.spec.ts
deleted file mode 100644
index 2a7c8f53..00000000
--- a/packages/styled/__tests__/parseStyle.spec.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { parseStyle } from '../src/parseStyle';
-
-describe('parseStyle', () => {
- test('simple', () => {
- const style = parseStyle({ lineHeight: 10, fontSize: 10 });
- expect(style).toEqual({ lineHeight: '10px', fontSize: 10 });
- });
- test('empty', () => {
- const style = parseStyle();
- expect(style).toEqual(undefined);
- });
-});
diff --git a/packages/styled/__tests__/plugins/pseudoClass.spec.ts b/packages/styled/__tests__/plugins/pseudoClass.spec.ts
new file mode 100644
index 00000000..141dab17
--- /dev/null
+++ b/packages/styled/__tests__/plugins/pseudoClass.spec.ts
@@ -0,0 +1,129 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+import { createStyles } from '../../src/createStyles';
+import { BasePlugin } from '../../src/plugins/Base';
+import { PseudoClassPlugin } from '../../src/plugins/PseudoClass';
+import { Registry } from '../../src/Registry';
+
+jest.mock('../../src/isWeb/isWeb', () => {
+ return { isWeb: true };
+});
+
+describe('PseudoClassPlugin', () => {
+ beforeAll(() => {
+ Registry.addPlugin(BasePlugin).addPlugin(PseudoClassPlugin);
+ });
+ test('basic', () => {
+ const style = createStyles(() => ({
+ container: {
+ 'base': { color: 'black' },
+ ':hover': { color: 'white' },
+ ':active': { color: 'red' },
+ },
+ }));
+ expect(style.container.style()).toStrictEqual({
+ style: { color: 'black' },
+ });
+ expect(style.container.style({})).toStrictEqual({
+ style: { color: 'black' },
+ });
+ expect(style.container.style({ hover: false })).toStrictEqual({
+ style: { color: 'black' },
+ });
+ expect(style.container.style({ hover: true })).toStrictEqual({
+ style: { color: 'white' },
+ });
+ });
+
+ test('className', () => {
+ const style = createStyles(() => ({
+ container: {
+ 'base': { color: 'black' },
+ ':hover': { color: 'white' },
+ ':active': { color: 'red' },
+ },
+ }));
+ expect(style.container.className()).toStrictEqual({
+ className: 'color-[black] hover:color-[white] active:color-[red]',
+ });
+ expect(style.container.className({})).toStrictEqual({
+ className: 'color-[black] hover:color-[white] active:color-[red]',
+ });
+ expect(style.container.className({ hover: true })).toStrictEqual({
+ className: 'hover:color-[white] color-[white] active:color-[red]',
+ });
+ expect(
+ style.container.className({ hover: true, active: true })
+ ).toStrictEqual({
+ className: 'hover:color-[white] active:color-[red] color-[red]',
+ });
+ expect(style.container.className({ active: true })).toStrictEqual({
+ className: 'hover:color-[white] active:color-[red] color-[red]',
+ });
+ });
+
+ test('rnw', () => {
+ const style = createStyles(() => ({
+ container: {
+ 'base': { color: 'black' },
+ ':hover': { color: 'white' },
+ ':active': { color: 'red' },
+ },
+ }));
+ expect(style.container.rnw()).toStrictEqual({
+ style: [
+ {
+ '$$css': true,
+ 'active:color-[red]': 'active:color-[red]',
+ 'color-[black]': 'color-[black]',
+ 'hover:color-[white]': 'hover:color-[white]',
+ },
+ ],
+ });
+ expect(style.container.rnw({})).toStrictEqual({
+ style: [
+ {
+ '$$css': true,
+ 'active:color-[red]': 'active:color-[red]',
+ 'color-[black]': 'color-[black]',
+ 'hover:color-[white]': 'hover:color-[white]',
+ },
+ ],
+ });
+ expect(style.container.rnw({ hover: true })).toStrictEqual({
+ style: [
+ {
+ '$$css': true,
+ 'active:color-[red]': 'active:color-[red]',
+ 'hover:color-[white]': 'hover:color-[white]',
+ 'color-[white]': 'color-[white]',
+ },
+ ],
+ });
+ expect(style.container.rnw({ active: true })).toStrictEqual({
+ style: [
+ {
+ '$$css': true,
+ 'active:color-[red]': 'active:color-[red]',
+ 'hover:color-[white]': 'hover:color-[white]',
+ 'color-[red]': 'color-[red]',
+ },
+ ],
+ });
+ expect(style.container.rnw({ active: true, hover: true })).toStrictEqual({
+ style: [
+ {
+ '$$css': true,
+ 'active:color-[red]': 'active:color-[red]',
+ 'hover:color-[white]': 'hover:color-[white]',
+ 'color-[red]': 'color-[red]',
+ },
+ ],
+ });
+ });
+});
diff --git a/packages/styled/__tests__/plugins/variants.spec.ts b/packages/styled/__tests__/plugins/variants.spec.ts
new file mode 100644
index 00000000..ff5195db
--- /dev/null
+++ b/packages/styled/__tests__/plugins/variants.spec.ts
@@ -0,0 +1,126 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+///
+
+import { PseudoClassPlugin } from '../../src/plugins';
+import { createStyles } from '../../src/createStyles';
+import { BasePlugin } from '../../src/plugins/Base';
+import { VariantsPlugin } from '../../src/plugins/Variants';
+import { Registry } from '../../src/Registry';
+
+jest.mock('../../src/isWeb/isWeb', () => {
+ return { isWeb: true };
+});
+
+describe('VariantsPlugin', () => {
+ beforeAll(() => {
+ Registry.addPlugin(BasePlugin)
+ .addPlugin(VariantsPlugin)
+ .addPlugin(PseudoClassPlugin);
+ });
+ test('style', () => {
+ const style = createStyles(() => ({
+ container: {
+ base: { color: 'black' },
+ variants: {
+ role: {
+ link: { 'base': { color: 'white' }, ':hover': { color: 'red' } },
+ },
+ },
+ },
+ }));
+ expect(style.container.style()).toStrictEqual({
+ style: { color: 'black' },
+ });
+ expect(style.container.style({})).toStrictEqual({
+ style: { color: 'black' },
+ });
+ expect(style.container.style({ variants: {} })).toStrictEqual({
+ style: { color: 'black' },
+ });
+ expect(style.container.style({ variants: { role: 'toto' } })).toStrictEqual(
+ { style: { color: 'black' } }
+ );
+ expect(style.container.style({ variants: { role: 'link' } })).toStrictEqual(
+ { style: { color: 'white' } }
+ );
+ expect(
+ style.container.style({ variants: { role: 'link' }, hover: true })
+ ).toStrictEqual({ style: { color: 'red' } });
+ });
+
+ test('className', () => {
+ const style = createStyles(() => ({
+ container: {
+ base: { color: 'black' },
+ variants: {
+ role: {
+ link: { 'base': { color: 'white' }, ':hover': { color: 'red' } },
+ },
+ },
+ },
+ }));
+ expect(style.container.className({})).toStrictEqual({
+ className: 'color-[black]',
+ });
+ expect(style.container.className({ variants: {} })).toStrictEqual({
+ className: 'color-[black]',
+ });
+ expect(
+ style.container.className({ variants: { role: 'toto' } })
+ ).toStrictEqual({ className: 'color-[black]' });
+ expect(
+ style.container.className({ variants: { role: 'link' } })
+ ).toStrictEqual({ className: 'color-[white] hover:color-[red]' });
+ expect(
+ style.container.className({ variants: { role: 'link' }, hover: true })
+ ).toStrictEqual({ className: 'hover:color-[red] color-[red]' });
+ });
+
+ test('rnw', () => {
+ const style = createStyles(() => ({
+ container: {
+ base: { color: 'black' },
+ variants: {
+ role: {
+ link: { 'base': { color: 'white' }, ':hover': { color: 'red' } },
+ },
+ },
+ },
+ }));
+ expect(style.container.rnw({})).toStrictEqual({
+ style: [{ '$$css': true, 'color-[black]': 'color-[black]' }],
+ });
+ expect(style.container.rnw({ variants: {} })).toStrictEqual({
+ style: [{ '$$css': true, 'color-[black]': 'color-[black]' }],
+ });
+ expect(style.container.rnw({ variants: { role: 'toto' } })).toStrictEqual({
+ style: [{ '$$css': true, 'color-[black]': 'color-[black]' }],
+ });
+ expect(style.container.rnw({ variants: { role: 'link' } })).toStrictEqual({
+ style: [
+ {
+ '$$css': true,
+ 'color-[white]': 'color-[white]',
+ 'hover:color-[red]': 'hover:color-[red]',
+ },
+ ],
+ });
+ expect(
+ style.container.rnw({ variants: { role: 'link' }, hover: true })
+ ).toStrictEqual({
+ style: [
+ {
+ '$$css': true,
+ 'color-[red]': 'color-[red]',
+ 'hover:color-[red]': 'hover:color-[red]',
+ },
+ ],
+ });
+ });
+});
diff --git a/packages/styled/__tests__/setTheme.spec.ts b/packages/styled/__tests__/setTheme.spec.ts
new file mode 100644
index 00000000..0f1e5701
--- /dev/null
+++ b/packages/styled/__tests__/setTheme.spec.ts
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+import { setTheme } from '../src/setTheme/setTheme';
+import { setTheme as setThemeWeb } from '../src/setTheme/setTheme.web';
+
+describe('setTheme', () => {
+ const removeOld = window.document.documentElement.classList.remove;
+ const addOld = window.document.documentElement.classList.add;
+ beforeEach(() => {
+ window.document.documentElement.classList.remove = jest.fn();
+ window.document.documentElement.classList.add = jest.fn();
+ });
+ afterEach(() => {
+ window.document.documentElement.classList.remove = removeOld;
+ window.document.documentElement.classList.add = addOld;
+ });
+ test('web', () => {
+ setThemeWeb('old', 'new');
+
+ expect(window.document.documentElement.classList.remove).toBeCalledWith(
+ 'old'
+ );
+ expect(window.document.documentElement.classList.add).toBeCalledWith('new');
+ });
+ test('native', () => {
+ expect(setTheme('old', 'new')).toBe(undefined);
+ });
+});
diff --git a/packages/styled/__tests__/styled.spec.tsx b/packages/styled/__tests__/styled.spec.tsx
deleted file mode 100644
index 77e39a56..00000000
--- a/packages/styled/__tests__/styled.spec.tsx
+++ /dev/null
@@ -1,200 +0,0 @@
-import { styled } from '../src/styled';
-import { Text } from 'react-native';
-import { createStyleSheet } from 'react-native-unistyles';
-import { render } from '@crossed/test';
-
-jest.mock('@preact/signals-react/runtime', () => {
- return { useSignals: jest.fn() };
-});
-jest.mock('react-native-unistyles', () => {
- const { ...actual } = jest.requireActual('react-native-unistyles');
- jest.spyOn(actual, 'createStyleSheet');
- jest.spyOn(actual, 'useStyles');
- return { __esModule: true, ...actual };
-});
-
-jest.mock('../src/useLogic', () => ({
- useLogic: jest
- .fn()
- .mockImplementation(() => ({ actions: {}, styles: { value: {} } })),
-}));
-
-describe('styled', () => {
- // eslint-disable-next-line no-console
- const log = console.log;
- beforeEach(() => {
- // eslint-disable-next-line no-console
- console.log = jest.fn();
- });
- afterEach(() => {
- // eslint-disable-next-line no-console
- console.log = log;
- jest.requireMock('react-native-unistyles').createStyleSheet.mockReset();
- });
-
- describe('without render', () => {
- test('all mock call with object', () => {
- styled(Text, {
- 'backgroundColor': 'red',
- 'hover:': { backgroundColor: 'gray' },
- 'active:': { backgroundColor: 'green' },
- });
- expect(createStyleSheet).toBeCalledTimes(0);
- // expect(
- // typeof (createStyleSheet as jest.Mock).mock.results[0].value
- // ).toEqual('function');
- // expect((createStyleSheet as jest.Mock).mock.results[0].value()).toEqual({
- // active: { backgroundColor: 'green' },
- // base: { backgroundColor: 'red' },
- // focus: {},
- // hover: { backgroundColor: 'gray' },
- // });
- });
-
- test('all mock call with function', () => {
- styled(Text, () => ({
- 'backgroundColor': 'red',
- 'hover:': { backgroundColor: 'gray' },
- 'active:': { backgroundColor: 'green' },
- }));
- expect(createStyleSheet).toBeCalledTimes(0);
- // expect(
- // typeof (createStyleSheet as jest.Mock).mock.results[0].value
- // ).toEqual('function');
- // expect((createStyleSheet as jest.Mock).mock.results[0].value()).toEqual({
- // active: { backgroundColor: 'green' },
- // base: { backgroundColor: 'red' },
- // focus: {},
- // hover: { backgroundColor: 'gray' },
- // });
- });
- });
-
- describe('with render', () => {
- test('all mock call with object', () => {
- const Body = styled(Text, {
- 'backgroundColor': 'red',
- 'hover:': { backgroundColor: 'gray' },
- 'active:': { backgroundColor: 'green' },
- });
-
- render( );
-
- expect(createStyleSheet).toBeCalledTimes(1);
-
- const { useLogic } = jest.requireMock('../src/useLogic');
- expect(useLogic).toBeCalled();
-
- const [params] = useLogic.mock.lastCall;
- expect(params).toHaveProperty('props', {});
- });
-
- test('all mock call with function', () => {
- const Body = styled(Text, () => ({
- 'backgroundColor': 'red',
- 'hover:': { backgroundColor: 'gray' },
- 'active:': { backgroundColor: 'green' },
- }));
-
- render(
);
-
- expect(createStyleSheet).toBeCalledTimes(1);
-
- const { useLogic } = jest.requireMock('../src/useLogic');
- expect(useLogic).toBeCalled();
-
- const [params] = useLogic.mock.lastCall;
- expect(params).toHaveProperty('debug', undefined);
- expect(params).toHaveProperty('name', 'Text');
- expect(params).toHaveProperty('props', {});
- expect(params).toHaveProperty('styles', {});
- });
- });
-
- test('debug', () => {
- const Body = styled(
- Text,
- () => ({
- 'backgroundColor': 'red',
- 'hover:': { backgroundColor: 'gray' },
- 'active:': { backgroundColor: 'green' },
- }),
- { debug: true }
- );
-
- render( );
-
- // eslint-disable-next-line no-console
- expect(console.log).toBeCalledTimes(3);
- });
-
- test('children function', () => {
- const Body = styled(Text, () => ({
- 'backgroundColor': 'red',
- 'hover:': { backgroundColor: 'gray' },
- 'active:': { backgroundColor: 'green' },
- }));
- const { container } = render(
-
- Hello
-
- );
-
- expect(container.innerHTML).toEqual(
- 'Hello
'
- );
- });
-
- describe('check styleSheet', () => {
- test('with function', () => {
- const Body = styled(Text, () => ({
- 'backgroundColor': 'red',
- 'hover:': { backgroundColor: 'gray' },
- 'active:': { backgroundColor: 'green' },
- }));
-
- expect(typeof Body.styleSheet).toEqual('function');
- expect(Body.styleSheet({} as never)).toEqual({
- active: { backgroundColor: 'green' },
- hover: { backgroundColor: 'gray' },
- focus: {},
- base: { backgroundColor: 'red' },
- });
- });
- test('with object', () => {
- const Body = styled(Text, {
- 'backgroundColor': 'red',
- 'hover:': { backgroundColor: 'gray' },
- 'active:': { backgroundColor: 'green' },
- });
-
- expect(typeof Body.styleSheet).toEqual('function');
- expect(Body.styleSheet({} as never)).toEqual({
- active: { backgroundColor: 'green' },
- hover: { backgroundColor: 'gray' },
- focus: {},
- base: { backgroundColor: 'red' },
- });
- });
-
- test('with extends', () => {
- const { createStyleSheet } = jest.requireMock('react-native-unistyles');
- createStyleSheet.mockImplementation((e: any) => e);
- const First = styled(Text, {
- backgroundColor: 'red',
- });
- const Body = styled(First, {
- 'hover:': { backgroundColor: 'gray' },
- 'active:': { backgroundColor: 'green' },
- });
-
- expect(typeof Body.styleSheet).toEqual('function');
- expect(Body.styleSheet({} as never)).toEqual({
- active: { backgroundColor: 'green' },
- hover: { backgroundColor: 'gray' },
- focus: {},
- base: { backgroundColor: 'red' },
- });
- });
- });
-});
diff --git a/packages/styled/__tests__/useLogic.spec.tsx b/packages/styled/__tests__/useLogic.spec.tsx
deleted file mode 100644
index 85ebaa72..00000000
--- a/packages/styled/__tests__/useLogic.spec.tsx
+++ /dev/null
@@ -1,302 +0,0 @@
-import type { ReturnExtract } from '../src/extract';
-import { useLogic } from '../src/useLogic';
-
-jest.mock('react', () => {
- return {
- ...jest.requireActual('react'),
- useMemo: jest.fn().mockImplementation((c) => c()),
- };
-});
-
-jest.mock('@preact/signals-react', () => {
- return {
- effect: jest.fn((c) => c()),
- signal: jest.fn((e: any) => ({ value: e })),
- };
-});
-jest.mock('../src/hooks/useHover', () => {
- return {
- useHover: jest.fn().mockImplementation(() => ({
- hovered: { value: false },
- actions: { onHover: () => {} },
- })),
- };
-});
-jest.mock('../src/hooks/useActive', () => {
- return {
- useActive: jest.fn().mockImplementation(() => ({
- active: { value: false },
- actions: { onActive: () => {} },
- })),
- };
-});
-jest.mock('../src/hooks/useFocus', () => {
- return {
- useFocus: jest.fn().mockImplementation(() => ({
- focus: { value: false },
- actions: { onFocus: () => {} },
- })),
- };
-});
-
-const styles: Partial = {
- base: {},
- hover: {},
- active: {},
- focus: {},
-};
-
-describe('useLogic', () => {
- // eslint-disable-next-line no-console
- const log = console.log;
- beforeEach(() => {
- jest.requireMock('react-native-unistyles').useStyles.mockClear();
- jest.requireMock('react').useMemo.mockClear();
- jest.requireMock('../src/hooks/useHover').useHover.mockClear();
- jest.requireMock('../src/hooks/useActive').useActive.mockClear();
- jest.requireMock('../src/hooks/useFocus').useFocus.mockClear();
- // eslint-disable-next-line no-console
- console.log = jest.fn();
- });
- afterEach(() => {
- // eslint-disable-next-line no-console
- console.log = log;
- });
- test('basic', () => {
- const props = { style: {} };
- const data = useLogic({ props, styles });
- const { useHover } = jest.requireMock('../src/hooks/useHover');
- const { useActive } = jest.requireMock('../src/hooks/useActive');
- const { useFocus } = jest.requireMock('../src/hooks/useFocus');
- const { effect, signal } = jest.requireMock('@preact/signals-react');
-
- expect(useHover).toBeCalledWith(props);
- expect(useActive).toBeCalledWith(props);
- expect(useFocus).toBeCalledWith(props);
-
- expect(data).toHaveProperty('actions');
- expect(data).toHaveProperty('styles');
- expect(data.styles).toEqual({ value: {} });
- expect(Object.keys(data.actions)).toEqual([
- 'onActive',
- 'onHover',
- 'onFocus',
- ]);
-
- expect(effect).toBeCalled();
- expect(signal).toBeCalledTimes(2);
- });
-
- test('hover', () => {
- const props = {};
- const data = useLogic({ props, styles });
- const { useHover } = jest.requireMock('../src/hooks/useHover');
- const { useActive } = jest.requireMock('../src/hooks/useActive');
- const { useFocus } = jest.requireMock('../src/hooks/useFocus');
-
- expect(useHover).toBeCalledWith(props);
- expect(useActive).toBeCalledWith(props);
- expect(useFocus).toBeCalledWith(props);
-
- expect(data).toHaveProperty('actions');
- expect(data).toHaveProperty('styles');
- expect(data.styles).toEqual({ value: {} });
- expect(Object.keys(data.actions)).toEqual([
- 'onActive',
- 'onHover',
- 'onFocus',
- ]);
- });
-
- test('active', () => {
- const props = {};
- const data = useLogic({ props, styles });
- const { useHover } = jest.requireMock('../src/hooks/useHover');
- const { useActive } = jest.requireMock('../src/hooks/useActive');
- const { useFocus } = jest.requireMock('../src/hooks/useFocus');
-
- expect(useHover).toBeCalledWith(props);
- expect(useActive).toBeCalledWith(props);
- expect(useFocus).toBeCalledWith(props);
-
- expect(data).toHaveProperty('actions');
- expect(data).toHaveProperty('styles');
- expect(data.styles).toEqual({ value: {} });
- expect(Object.keys(data.actions)).toEqual([
- 'onActive',
- 'onHover',
- 'onFocus',
- ]);
- });
-
- test('focus', () => {
- const props = {};
- const data = useLogic({ props, styles });
- const { useHover } = jest.requireMock('../src/hooks/useHover');
- const { useActive } = jest.requireMock('../src/hooks/useActive');
- const { useFocus } = jest.requireMock('../src/hooks/useFocus');
-
- expect(useHover).toBeCalledWith(props);
- expect(useActive).toBeCalledWith(props);
- expect(useFocus).toBeCalledWith(props);
-
- expect(data).toHaveProperty('actions');
- expect(data).toHaveProperty('styles');
- expect(data.styles).toEqual({ value: {} });
- expect(Object.keys(data.actions)).toEqual([
- 'onActive',
- 'onHover',
- 'onFocus',
- ]);
- });
-
- test('all', () => {
- const props = {};
- const { useHover } = jest.requireMock('../src/hooks/useHover');
- const { useActive } = jest.requireMock('../src/hooks/useActive');
- const { useFocus } = jest.requireMock('../src/hooks/useFocus');
-
- useHover.mockImplementation(() => ({
- hovered: { value: true },
- actions: { onHover: () => {} },
- }));
- useActive.mockImplementation(() => ({
- active: { value: true },
- actions: { onActive: () => {} },
- }));
- useFocus.mockImplementation(() => ({
- focus: { value: true },
- actions: { onFocus: () => {} },
- }));
-
- const data = useLogic({
- props,
- styles,
- });
-
- expect(useHover).toBeCalledWith(props);
- expect(useActive).toBeCalledWith(props);
- expect(useFocus).toBeCalledWith(props);
-
- expect(data).toHaveProperty('actions');
- expect(data).toHaveProperty('styles');
- expect(data.styles).toEqual({ value: {} });
- expect(Object.keys(data.actions)).toEqual([
- 'onActive',
- 'onHover',
- 'onFocus',
- ]);
- });
-
- test('with extraStyle', () => {
- const props = {};
- const extraStyle = jest.fn();
- const data = useLogic({
- props,
- styles: {
- base: { extraStyle } as any,
- hover: {},
- active: {},
- focus: {},
- },
- });
- const { useHover } = jest.requireMock('../src/hooks/useHover');
- const { useActive } = jest.requireMock('../src/hooks/useActive');
- const { useFocus } = jest.requireMock('../src/hooks/useFocus');
-
- expect(useHover).toBeCalledWith(props);
- expect(useActive).toBeCalledWith(props);
- expect(useFocus).toBeCalledWith(props);
-
- expect(data).toHaveProperty('actions');
- expect(data).toHaveProperty('styles');
- expect(Object.keys(data.actions)).toEqual([
- 'onActive',
- 'onHover',
- 'onFocus',
- ]);
-
- expect(extraStyle).toBeCalledWith(props, {
- focus: true,
- active: true,
- hover: true,
- });
- });
-
- test('with style', () => {
- const props = { style: [{ color: 'white' }] };
- const extraStyle = jest.fn(() => ({ color: 'violet' }));
- const data = useLogic({
- props,
- styles: {
- base: { color: 'red', extraStyle } as any,
- hover: { color: 'orange' },
- active: { color: 'green' },
- focus: { color: 'blue' },
- },
- });
- const { useHover } = jest.requireMock('../src/hooks/useHover');
- const { useActive } = jest.requireMock('../src/hooks/useActive');
- const { useFocus } = jest.requireMock('../src/hooks/useFocus');
-
- expect(useHover).toBeCalledWith(props);
- expect(useActive).toBeCalledWith(props);
- expect(useFocus).toBeCalledWith(props);
-
- expect(data).toHaveProperty('actions');
- expect(data).toHaveProperty('styles');
- expect(data.styles).toEqual({
- value: { color: 'white' },
- });
- expect(Object.keys(data.actions)).toEqual([
- 'onActive',
- 'onHover',
- 'onFocus',
- ]);
-
- expect(extraStyle).toBeCalledWith(props, {
- focus: true,
- active: true,
- hover: true,
- });
- });
-
- test('debug', () => {
- const props = {};
- const extraStyle = jest.fn();
- const data = useLogic({
- props,
- debug: true,
- styles: {
- base: { extraStyle } as any,
- hover: {},
- active: {},
- focus: {},
- },
- });
- const { useHover } = jest.requireMock('../src/hooks/useHover');
- const { useActive } = jest.requireMock('../src/hooks/useActive');
- const { useFocus } = jest.requireMock('../src/hooks/useFocus');
-
- expect(useHover).toBeCalledWith(props);
- expect(useActive).toBeCalledWith(props);
- expect(useFocus).toBeCalledWith(props);
-
- expect(data).toHaveProperty('actions');
- expect(data).toHaveProperty('styles');
- expect(Object.keys(data.actions)).toEqual([
- 'onActive',
- 'onHover',
- 'onFocus',
- ]);
-
- // eslint-disable-next-line no-console
- expect(console.log).toBeCalledTimes(2);
-
- expect(extraStyle).toBeCalledWith(props, {
- focus: true,
- active: true,
- hover: true,
- });
- });
-});
diff --git a/packages/styled/__tests__/useTheme.spec.ts b/packages/styled/__tests__/useTheme.spec.ts
new file mode 100644
index 00000000..2761703b
--- /dev/null
+++ b/packages/styled/__tests__/useTheme.spec.ts
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+import { Registry } from '../src/Registry';
+import { useTheme } from '../src/useTheme';
+import { renderHook, act } from '@crossed/test';
+
+describe('useTheme', () => {
+ test('no theme', () => {
+ Registry.setThemes({ dark: {}, light: {} });
+ const { result } = renderHook(() => useTheme());
+ expect(result.current).toEqual({});
+ });
+ test('with theme', () => {
+ Registry.setThemes({
+ dark: { dark: 'dark' },
+ light: { light: 'light' },
+ });
+ const { result, rerender } = renderHook(() => useTheme());
+ expect(result.current).toEqual({});
+
+ act(() => {
+ Registry.setThemeName('dark');
+ });
+ rerender();
+ expect(result.current).toEqual({ dark: 'var(--dark)' });
+ act(() => {
+ Registry.setThemeName('light');
+ });
+ rerender();
+ expect(result.current).toEqual({ light: 'var(--light)' });
+ });
+});
diff --git a/packages/styled/package.json b/packages/styled/package.json
index 75cec796..cfcbf40f 100644
--- a/packages/styled/package.json
+++ b/packages/styled/package.json
@@ -1,13 +1,13 @@
{
"name": "@crossed/styled",
"description": "A universal & performant styling library for React Native, Next.js & React",
- "version": "0.13.0",
- "main": "lib/commonjs/index",
+ "version": "0.14.0-beta.5",
"types": "lib/typescript/index.d.ts",
- "module": "lib/module/index",
- "react-native": "src/index",
- "source": "src/index",
"typings": "lib/typescript/index.d.ts",
+ "main": "lib/commonjs/index.js",
+ "module": "lib/module/index.js",
+ "react-native": "./src/index.ts",
+ "source": "./src/index.ts",
"keywords": [
"React Native",
"Next.js",
@@ -28,93 +28,74 @@
"theming",
"generator"
],
+ "scripts": {
+ "clean": "rm -rf lib",
+ "type-check": "tsc --noEmit",
+ "test": "crossed-test",
+ "watch": "crossed-build --watch",
+ "build": "crossed-build"
+ },
+ "files": [
+ "lib/",
+ "src/"
+ ],
"exports": {
+ "./package.json": "./package.json",
".": {
"require": "./lib/commonjs/index.js",
"import": "./lib/module/index.js",
"default": "./lib/module/index.js",
+ "react-native": "./src/index.ts",
"types": "./lib/typescript/index.d.ts"
},
- "./styled": {
- "require": "./lib/commonjs/styled.js",
- "import": "./lib/module/styled.js",
- "default": "./lib/module/styled.js",
- "types": "./lib/typescript/styled.d.ts"
- },
- "./unistyles": {
- "require": "./lib/commonjs/unistyles.js",
- "import": "./lib/module/unistyles.js",
- "default": "./lib/module/unistyles.js",
- "types": "./lib/typescript/unistyles.d.ts"
- },
"./registry": {
"require": "./lib/commonjs/Registry.js",
"import": "./lib/module/Registry.js",
"default": "./lib/module/Registry.js",
+ "react-native": "./src/Registry.ts",
"types": "./lib/typescript/Registry.d.ts"
+ },
+ "./plugins": {
+ "require": "./lib/commonjs/plugins/index.js",
+ "import": "./lib/module/plugins/index.js",
+ "default": "./lib/module/plugins/index.js",
+ "react-native": "./src/plugins/index.ts",
+ "types": "./lib/typescript/plugins/index.d.ts"
}
},
"typesVersions": {
"*": {
- "index": [
- "lib/typescript/index.d.ts"
- ],
- "styled": [
- "lib/typescript/styled.d.ts"
- ],
- "unistyles": [
- "lib/typescript/unistyles.d.ts"
+ ".": [
+ "./lib/typescript/index.d.ts"
],
"registry": [
- "lib/typescript/Registry.d.ts"
+ "./lib/typescript/Registry.d.ts"
+ ],
+ "plugins": [
+ "./lib/typescript/plugins/index.d.ts"
]
}
},
- "scripts": {
- "clean": "rm -rf lib",
- "watch": "crossed-build --watch --project-path ./tsconfig.build.json",
- "build": "crossed-build --project-path ./tsconfig.build.json",
- "dev:web": "cd example/native && yarn web --clear",
- "test": "crossed-test",
- "dev": "ts-node-dev --respawn --transpile-only ./src/cva/index.ts",
- "type-check": "tsc --noEmit"
- },
"devDependencies": {
- "@crossed/build": "*",
- "@crossed/test": "*",
- "@jest/types": "28.1.1",
- "@swc/jest": "^0.2.29",
- "@testing-library/jest-dom": "^6.1.3",
- "@testing-library/react": "^14.0.0",
- "@testing-library/react-native": "^12.4.3",
- "@testing-library/user-event": "^14.5.1",
+ "@crossed/build": "^0.5.0",
+ "@crossed/test": "0.11.0",
"@types/jest": "^29.5.0",
- "@types/node": "18.11.18",
- "@types/react": "^18.2.21",
- "@types/react-dom": "18.2.7",
- "@types/react-native": "^0.72.5",
- "babel-plugin-transform-remove-console": "^6.9.4",
- "jest": "^29.5.0",
- "next": "14.0.4",
+ "@types/react": "^18.2.64",
+ "@types/react-dom": "^18.2.18",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "react-native": "^0.72.5",
- "ts-node": "10.8.1",
- "ts-node-dev": "^2.0.0",
- "tsconfig": "*",
- "typescript": "5.1.6"
+ "react-native": "^0.73.5",
+ "react-native-web": "^0.19.10",
+ "ts-loader": "^9.5.1",
+ "typescript": "^5.4.2"
},
"peerDependencies": {
- "next": "14.0.4"
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-native": "^0.73.5",
+ "react-native-web": "^0.19.10"
},
"dependencies": {
- "@crossed/core": "*",
- "@preact/signals": "^1.2.2",
- "@preact/signals-react": "^2.0.0",
- "react-native-unistyles": "^2.0.0"
- },
- "files": [
- "lib/",
- "src/"
- ]
+ "@crossed/core": "^0.8.1-beta.1"
+ }
}
diff --git a/packages/styled/src/Registry.ts b/packages/styled/src/Registry.ts
new file mode 100644
index 00000000..a0b99efe
--- /dev/null
+++ b/packages/styled/src/Registry.ts
@@ -0,0 +1,149 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+import type {
+ CrossedstyleValues,
+ Plugin,
+ PluginContext,
+ Themes,
+} from './types';
+import { setTheme } from './setTheme';
+import { convertKeyToCss, normalizeUnitPixel } from './plugins';
+
+export const parse = >(
+ t: T,
+ parentName?: string,
+ isWeb?: boolean
+): {
+ theme: T;
+ values: Record;
+} => {
+ return !isWeb
+ ? { theme: t, values: t }
+ : Object.entries(t).reduce<{
+ theme: T;
+ values: Record;
+ }>(
+ (acc, [key, value]) => {
+ if (Array.isArray(value)) {
+ } else if (['number', 'string'].includes(typeof value)) {
+ const name = convertKeyToCss(
+ `--${parentName ? `${parentName}-` : ''}${key}`
+ );
+ (acc.theme as any)[key] = `var(${name})`;
+ acc.values[name] = normalizeUnitPixel('marginTop', value, isWeb);
+ } else if (typeof value === 'object') {
+ const toto = parse(value, key, isWeb);
+ acc.theme = {
+ ...acc.theme,
+ ...Object.entries(toto.theme).reduce(
+ (acc2, [key2, value2]) => {
+ (acc2 as any)[key] = { ...acc2[key], [key2]: value2 };
+ return acc2;
+ },
+ {} as T
+ ),
+ };
+ acc.values = {
+ ...acc.values,
+ ...toto.values,
+ };
+ }
+ return acc;
+ },
+ { theme: {} as T, values: {} }
+ );
+};
+
+export class RegistryBridge {
+ private plugins: Plugin[] = [];
+
+ private _debug = false;
+
+ private _listen = new Set<
+ (_themeName: keyof Themes) => Promise | void
+ >();
+ private themes?: Themes;
+ private _themeName?: keyof Themes;
+
+ setThemes(themes: Partial) {
+ this.themes = { ...this.themes, ...themes };
+ return this;
+ }
+
+ get themeName() {
+ return this._themeName;
+ }
+
+ setThemeName(themeName: keyof Themes) {
+ setTheme(this._themeName, themeName);
+ this._themeName = themeName;
+ this._listen.forEach((cb) => cb(themeName));
+ return this;
+ }
+
+ getTheme() {
+ if (!this.themes) {
+ // throw new Error('themes are not set');
+ console.warn('Themes are not set');
+ return {} as Themes[keyof Themes];
+ }
+ return parse(this.themes[this.themeName] || {}, undefined, true)
+ .theme as Themes[keyof Themes];
+ }
+ getThemes() {
+ return this.themes;
+ }
+
+ subscribe(cb: (_themeName: keyof Themes) => Promise | void) {
+ this._listen.add(cb);
+ return () => {
+ this._listen.delete(cb);
+ };
+ }
+
+ setDebug(d: boolean) {
+ this._debug = d;
+ return this;
+ }
+
+ addPlugin(plugin: Plugin) {
+ this.plugins.push(plugin as any);
+ return this;
+ }
+
+ getPlugins() {
+ return this.plugins as typeof this.plugins;
+ }
+
+ log(e: string) {
+ if (this._debug) {
+ // eslint-disable-next-line no-console
+ console.log(`[@crossed/styled] ${e}`);
+ }
+ }
+
+ apply(
+ params: () => Record,
+ options: Omit>, 'styles' | 'key'>
+ ) {
+ this.log(`Registry apply`);
+ Object.entries(params()).forEach(
+ ([key, styles]: [string, CrossedstyleValues]) => {
+ this.plugins.forEach(({ test, apply, name }) => {
+ const keyFind = key.match(new RegExp(test, 'g'));
+ if (test && keyFind && keyFind.length > 0) {
+ this.log(`[${name}] Find "${key}" for "${name}" plugin`);
+ apply?.({ ...options, key, styles });
+ }
+ });
+ }
+ );
+ }
+}
+
+export const Registry = new RegistryBridge();
diff --git a/packages/styled/src/Registry.tsx b/packages/styled/src/Registry.tsx
deleted file mode 100644
index 020b1c0c..00000000
--- a/packages/styled/src/Registry.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * Copyright (c) Paymium.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root of this projects source tree.
- */
-
-'use client';
-
-import { useServerInsertedHTML } from 'next/navigation';
-import type { PropsWithChildren } from 'react';
-import { AppRegistry } from 'react-native';
-import { Main } from 'next/document';
-
-export const Registry = ({ children }: PropsWithChildren) => {
- useServerInsertedHTML(() => {
- AppRegistry.registerComponent('Main', () => Main);
-
- const { getStyleElement } = (AppRegistry as any).getApplication('Main');
- return <>{getStyleElement()}>;
- });
- return children;
-};
diff --git a/packages/styled/src/Slot.tsx b/packages/styled/src/Slot.tsx
deleted file mode 100644
index 1675a10a..00000000
--- a/packages/styled/src/Slot.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-/**
- * Copyright (c) Paymium.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root of this projects source tree.
- */
-
-'use client';
-import { composeRefs } from '@crossed/core';
-import * as React from 'react';
-
-/* -------------------------------------------------------------------------------------------------
- * Slot
- * -----------------------------------------------------------------------------------------------*/
-
-interface SlotProps extends React.HTMLAttributes {
- children?: React.ReactNode;
-}
-
-const Slot = React.forwardRef((props, forwardedRef) => {
- const { children, ...slotProps } = props;
- const childrenArray = React.Children.toArray(children);
- const slottable = childrenArray.find(isSlottable);
-
- if (slottable) {
- // the new element to render is the one passed as a child of `Slottable`
- const newElement = slottable.props.children as React.ReactNode;
-
- const newChildren = childrenArray.map((child) => {
- if (child === slottable) {
- // because the new element will be the one rendered, we are only interested
- // in grabbing its children (`newElement.props.children`)
- if (React.Children.count(newElement) > 1)
- return React.Children.only(null);
- return React.isValidElement(newElement)
- ? (newElement.props.children as React.ReactNode)
- : null;
- } else {
- return child;
- }
- });
- return (
-
- {React.isValidElement(newElement)
- ? React.cloneElement(newElement, undefined, newChildren)
- : null}
-
- );
- }
-
- return (
-
- {children}
-
- );
-});
-
-Slot.displayName = 'Slot';
-
-/* -------------------------------------------------------------------------------------------------
- * SlotClone
- * -----------------------------------------------------------------------------------------------*/
-
-interface SlotCloneProps {
- children: React.ReactNode;
-}
-
-const SlotClone = React.forwardRef(
- (props, forwardedRef) => {
- const { children, ...slotProps } = props;
-
- if (React.isValidElement(children)) {
- return React.cloneElement(children, {
- ...mergeProps(slotProps, children.props),
- ref: forwardedRef
- ? composeRefs(forwardedRef, (children as any).ref)
- : (children as any).ref,
- } as any);
- }
-
- return React.Children.count(children) > 1
- ? React.Children.only(null)
- : null;
- }
-);
-
-SlotClone.displayName = 'SlotClone';
-
-/* -------------------------------------------------------------------------------------------------
- * Slottable
- * -----------------------------------------------------------------------------------------------*/
-
-const Slottable = ({ children }: { children: React.ReactNode }) => {
- return <>{children}>;
-};
-
-/* ---------------------------------------------------------------------------------------------- */
-
-type AnyProps = Record;
-
-function isSlottable(child: React.ReactNode): child is React.ReactElement {
- return React.isValidElement(child) && child.type === Slottable;
-}
-
-function mergeProps(slotProps: AnyProps, childProps: AnyProps) {
- // all child props should override
- const overrideProps = { ...childProps };
-
- for (const propName in childProps) {
- const slotPropValue = slotProps[propName];
- const childPropValue = childProps[propName];
-
- const isHandler = /^on[A-Z]/.test(propName);
- if (isHandler) {
- // if the handler exists on both, we compose them
- if (slotPropValue && childPropValue) {
- overrideProps[propName] = (...args: unknown[]) => {
- childPropValue(...args);
- slotPropValue(...args);
- };
- }
- // but if it exists only on the slot, we use only this one
- else if (slotPropValue) {
- overrideProps[propName] = slotPropValue;
- }
- }
- // if it's `style`, we merge them
- else if (propName === 'style') {
- overrideProps[propName] = { ...slotPropValue, ...childPropValue };
- } else if (propName === 'className') {
- overrideProps[propName] = [slotPropValue, childPropValue]
- .filter(Boolean)
- .join(' ');
- }
- }
-
- return { ...slotProps, ...overrideProps };
-}
-
-export { Slot, Slottable };
-export type { SlotProps };
diff --git a/packages/styled/src/createStyles.ts b/packages/styled/src/createStyles.ts
new file mode 100644
index 00000000..e7de30b3
--- /dev/null
+++ b/packages/styled/src/createStyles.ts
@@ -0,0 +1,163 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+import type {
+ CrossedMethods,
+ CrossedPropsExtended,
+ PluginContext,
+ Themes,
+} from './types';
+import { Registry, parse } from './Registry';
+import { isWeb } from './isWeb';
+
+const cleanClassName = (classNames: string[]) => {
+ return classNames.reduce((acc, className) => {
+ const [property] = className.match(/^([a-z\-]+)/g) || [];
+ if (property) {
+ acc.forEach((accKey) => {
+ const [same] = accKey.match(new RegExp(`^${property}`, 'g')) || [];
+ if (same) {
+ acc.delete(accKey);
+ }
+ });
+ }
+ acc.add(className);
+ return acc;
+ }, new Set());
+};
+
+export const createStyles = (
+ stylesParam: (_theme: Themes[keyof Themes]) => Record
+) => {
+ const { theme: themeParsed } = parse(Registry.getTheme(), undefined, isWeb);
+ const results = stylesParam(themeParsed);
+ const apply = (
+ style: Record,
+ props: CrossedPropsExtended,
+ addClassname: PluginContext['addClassname']
+ ) => {
+ Registry.apply(() => style, {
+ isWeb,
+ props,
+ addClassname,
+ });
+ };
+
+ return Object.entries(results).reduce<
+ Record>>
+ >((acc, [keyStyle, styleOfKey]: [C, S]) => {
+ acc[keyStyle] = {
+ style: (props: CrossedPropsExtended = {}) => {
+ let style = {} as any;
+ const parentStyle = (
+ Array.isArray(props.style) ? props.style : [props.style]
+ ).reduce((acc, st) => {
+ if (!st || st.$$css) return acc;
+ if (!st.$$css) {
+ acc = { ...acc, ...st };
+ }
+ return acc;
+ }, {});
+ apply(styleOfKey, props, ({ body, suffix, wrapper, prefix }) => {
+ if (!suffix && !wrapper && !prefix) {
+ style = {
+ ...style,
+ ...Object.values(body).reduce((acc, e) => ({ ...acc, ...e }), {}),
+ };
+ }
+ });
+ return {
+ style: {
+ ...style,
+ ...parentStyle,
+ },
+ };
+ },
+ className: (props: CrossedPropsExtended = {}) => {
+ const classNames: string[] = props.className
+ ? props.className.split(' ')
+ : [];
+ const parentStyle = (
+ Array.isArray(props.style) ? props.style : [props.style]
+ ).reduce((acc, st) => {
+ if (!st) return acc;
+ if (!st.$$css) {
+ acc = { ...acc, ...st };
+ } else if (st.$$css) {
+ const { $$css, ...otherClassName } = st;
+ classNames.push(...Object.keys(otherClassName));
+ }
+ return acc;
+ }, {});
+ apply(
+ {
+ ...styleOfKey,
+ base: { ...(styleOfKey as any).base, ...parentStyle },
+ },
+ props,
+ ({ body }) => {
+ classNames.push(...Object.keys(body));
+ }
+ );
+ return {
+ className: Array.from(cleanClassName(classNames).values()).join(' '),
+ };
+ },
+ rnw: (props: CrossedPropsExtended = {}) => {
+ const classNames: string[] = props.className
+ ? props.className.split(' ')
+ : [];
+ const parentStyle = (
+ Array.isArray(props.style) ? props.style : [props.style]
+ ).reduce((acc, st) => {
+ if (!st) return acc;
+ if (!st.$$css) {
+ acc = { ...acc, ...st };
+ } else if (st.$$css) {
+ const { $$css, ...otherClassName } = st;
+ classNames.push(...Object.keys(otherClassName));
+ }
+ return acc;
+ }, {});
+ apply(
+ {
+ ...styleOfKey,
+ base: { ...(styleOfKey as any).base, ...parentStyle },
+ },
+ props,
+ ({ body }) => {
+ classNames.push(...Object.keys(body));
+ }
+ );
+
+ (Array.isArray(props.style) ? props.style : [props.style]).forEach(
+ (st) => {
+ if (st && st.$$css) {
+ const { $$css, ...otherClassName } = st;
+ classNames.push(...Object.keys(otherClassName));
+ }
+ }
+ );
+
+ return {
+ style: [
+ Array.from(cleanClassName(classNames).values()).reduce<
+ Record
+ >(
+ (acc2, cl) => {
+ acc2[cl] = cl;
+ return acc2;
+ },
+ { $$css: true }
+ ),
+ ],
+ };
+ },
+ };
+ return acc;
+ }, {} as any) as Record>>;
+};
diff --git a/packages/styled/src/extract.ts b/packages/styled/src/extract.ts
deleted file mode 100644
index a1f05ab3..00000000
--- a/packages/styled/src/extract.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-/**
- * Copyright (c) Paymium.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root of this projects source tree.
- */
-
-import type { UnistylesValues } from 'react-native-unistyles/lib/typescript/src/types/stylesheet';
-import type { ExtractUnistylesValues, UnistylesValuesExtends } from './types';
-
-export type ReturnExtract = ExtractUnistylesValues & { base: UnistylesValues };
-
-const stateToExtract = [
- // 'checked:',
- // 'readOnly:',
- // 'required:',
- // 'invalid:',
- 'focus:',
- // 'focusVisible:',
- 'hover:',
- // 'pressed:',
- 'active:',
- // 'loading:',
- // 'disabled:',
-];
-
-export const extract = (p: Partial): ReturnExtract => {
- return Object.entries(p).reduce(
- (acc, [key, value]) => {
- if (
- (key === 'variants' || !stateToExtract.includes(key)) &&
- typeof value === 'object'
- ) {
- Object.entries(extract(value as any)).forEach(([k, v]) => {
- if (Object.keys(v).length > 0) {
- (acc as any)[k][key] = v;
- }
- });
- }
- if (stateToExtract.includes(key) && typeof value === 'object') {
- (acc as any)[key.replace(':', '')] = value as any;
- }
- if (!stateToExtract.includes(key)) {
- (acc.base as any)[key] =
- typeof value === 'object' ? extract(value as any).base : value;
- }
- return acc;
- },
- {
- base: {},
- // checked: {},
- // readOnly: {},
- // required: {},
- // invalid: {},
- focus: {},
- // focusVisible: {},
- hover: {},
- // pressed: {},
- active: {},
- // loading: {},
- // disabled: {},
- }
- );
-};
diff --git a/packages/styled/src/hooks/useActive.ts b/packages/styled/src/hooks/useActive.ts
deleted file mode 100644
index 9df05d63..00000000
--- a/packages/styled/src/hooks/useActive.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * Copyright (c) Paymium.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root of this projects source tree.
- */
-
-'use client';
-
-import { composeEventHandlers } from '@crossed/core';
-import { useSignal } from '@preact/signals-react';
-import { useEffect } from 'react';
-
-export const useActive = <
- T extends {
- active?: boolean;
- onPointerUp?: (..._args: any[]) => void;
- onPointerDown?: (..._args: any[]) => void;
- }
->(
- props: T
-) => {
- const active = useSignal(false);
- const onPointerUp = composeEventHandlers(() => {
- active.value = false;
- }, props.onPointerUp);
- const onPointerDown = composeEventHandlers(() => {
- active.value = true;
- }, props.onPointerDown);
-
- useEffect(() => {
- if (props.active !== undefined && active.value !== props.active) {
- active.value = props.active;
- }
- }, [props.active]);
-
- return {
- active,
- actions: {
- onPointerUp,
- onPointerDown,
- },
- };
-};
diff --git a/packages/styled/src/hooks/useFocus.ts b/packages/styled/src/hooks/useFocus.ts
deleted file mode 100644
index 70a5ded3..00000000
--- a/packages/styled/src/hooks/useFocus.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-/**
- * Copyright (c) Paymium.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root of this projects source tree.
- */
-
-'use client';
-
-import { composeEventHandlers } from '@crossed/core';
-import { useSignal } from '@preact/signals-react';
-import { useEffect } from 'react';
-
-export const useFocus = <
- T extends {
- focus?: boolean;
- onFocus?: (..._args: any[]) => void;
- onBlur?: (..._args: any[]) => void;
- }
->(
- props: T
-) => {
- const focus = useSignal(false);
- const onFocus = composeEventHandlers(() => {
- focus.value = true;
- }, props.onFocus) as any;
- const onBlur = composeEventHandlers(() => {
- focus.value = false;
- }, props.onBlur) as any;
-
- useEffect(() => {
- if (props.focus !== undefined && focus.value !== props.focus) {
- focus.value = props.focus;
- }
- }, [props.focus]);
-
- return { focus: focus, actions: { onFocus, onBlur } };
-};
diff --git a/packages/styled/src/hooks/useHover.ts b/packages/styled/src/hooks/useHover.ts
deleted file mode 100644
index 786f0952..00000000
--- a/packages/styled/src/hooks/useHover.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * Copyright (c) Paymium.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root of this projects source tree.
- */
-
-'use client';
-import { composeEventHandlers } from '@crossed/core';
-import { useSignal } from '@preact/signals-react';
-import { useEffect } from 'react';
-
-export const useHover = <
- T extends {
- hovered?: boolean;
- onPointerEnter?: (..._args: any[]) => void;
- onPointerLeave?: (..._args: any[]) => void;
- }
->(
- props: T
-) => {
- const hovered = useSignal(false);
- const onPointerEnter = composeEventHandlers(() => {
- if (props.hovered === undefined || props.hovered === false) {
- hovered.value = true;
- }
- }, props.onPointerEnter);
- const onPointerLeave = composeEventHandlers(() => {
- if (props.hovered === undefined || props.hovered === false) {
- hovered.value = false;
- }
- }, props.onPointerLeave);
-
- useEffect(() => {
- if (props.hovered !== undefined && hovered.value !== props.hovered) {
- hovered.value = props.hovered;
- }
- }, [props.hovered]);
-
- return {
- hovered,
- actions: {
- onPointerEnter: onPointerEnter,
- onPointerLeave: onPointerLeave,
- },
- };
-};
diff --git a/packages/styled/src/index.ts b/packages/styled/src/index.ts
index 76c2e286..0502912e 100644
--- a/packages/styled/src/index.ts
+++ b/packages/styled/src/index.ts
@@ -5,10 +5,7 @@
* LICENSE file in the root of this projects source tree.
*/
-import 'react-native-unistyles';
-
-export { styled } from './styled';
-export { parseStyle } from './parseStyle';
-export * from './unistyles';
+export { createStyles } from './createStyles';
export { Registry } from './Registry';
-export * from '@crossed/core';
+export type * from './types';
+export { useTheme } from './useTheme';
diff --git a/packages/styled/src/isWeb/index.ts b/packages/styled/src/isWeb/index.ts
new file mode 100644
index 00000000..d773326c
--- /dev/null
+++ b/packages/styled/src/isWeb/index.ts
@@ -0,0 +1,8 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+export { isWeb } from './isWeb';
diff --git a/packages/styled/src/isWeb/isWeb.ts b/packages/styled/src/isWeb/isWeb.ts
new file mode 100644
index 00000000..69becdd8
--- /dev/null
+++ b/packages/styled/src/isWeb/isWeb.ts
@@ -0,0 +1,8 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+export const isWeb = false;
diff --git a/packages/styled/src/isWeb/isWeb.web.ts b/packages/styled/src/isWeb/isWeb.web.ts
new file mode 100644
index 00000000..7a3d6ee3
--- /dev/null
+++ b/packages/styled/src/isWeb/isWeb.web.ts
@@ -0,0 +1,8 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+export const isWeb = true;
diff --git a/packages/styled/src/parseStyle.tsx b/packages/styled/src/parseStyle.tsx
deleted file mode 100644
index 0ed6686f..00000000
--- a/packages/styled/src/parseStyle.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * Copyright (c) Paymium.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root of this projects source tree.
- */
-
-const propertyToTransformInPixel = ['lineHeight'];
-export const parseStyle = (style?: any) => {
- return style
- ? Object.entries(style).reduce((acc, [key, v]) => {
- acc[key] = propertyToTransformInPixel.includes(key) ? `${v}px` : v;
- return acc;
- }, style)
- : style;
-};
diff --git a/packages/styled/src/plugins/Base.ts b/packages/styled/src/plugins/Base.ts
new file mode 100644
index 00000000..6e7cdd0b
--- /dev/null
+++ b/packages/styled/src/plugins/Base.ts
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+import type { CrossedstyleValues, Plugin } from '../types';
+import { convertKeyToCss, normalizeUnitPixel } from './utils';
+
+export interface CrossedBasePlugin {
+ base?: CrossedstyleValues;
+}
+
+export const BasePlugin: Plugin = {
+ name: 'BasePlugin',
+ test: '^base$',
+ apply: (context) => {
+ /**
+ * {".bg-color": { backgroundColor: "black" } }
+ */
+ context.addClassname({
+ body: Object.entries(context.styles).reduce<
+ Record
+ >((acc, [key, value]) => {
+ const valueNormalized = normalizeUnitPixel(key, value, context.isWeb);
+ acc[`${convertKeyToCss(key)}-[${valueNormalized}]`] = {
+ [key]: valueNormalized,
+ };
+ return acc;
+ }, {}),
+ });
+ },
+};
diff --git a/packages/styled/src/plugins/MediaQueries.ts b/packages/styled/src/plugins/MediaQueries.ts
new file mode 100644
index 00000000..9654a483
--- /dev/null
+++ b/packages/styled/src/plugins/MediaQueries.ts
@@ -0,0 +1,81 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+import type { CrossedstyleValues, Plugin } from '../types';
+import { convertKeyToCss, normalizeUnitPixel } from './utils';
+
+export interface CrossedMediaQueriesPlugin {
+ media?: Partial>;
+}
+
+export const MediaQueriesPlugin = >(
+ breakpoints: B
+): Plugin> => {
+ return {
+ name: 'MediaQueriesPlugin',
+ test: '^media$',
+ apply: function MediaQueriesApply({ styles, addClassname, props, isWeb }) {
+ let Dimensions: any;
+ let Platform: any;
+ // props only exists at runtime
+ // Hack for load react-native only at runtime, not buildtime
+ if (props) {
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
+ Dimensions = require('react-native').Dimensions;
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
+ Platform = require('react-native').Platform;
+ }
+ Object.entries(styles).forEach(([key, values]) => {
+ const breakpointsValue = normalizeUnitPixel(
+ 'width',
+ breakpoints[key],
+ isWeb
+ );
+ if (values) {
+ Object.entries(values).forEach(([keyProperty, valueProperty]) => {
+ if (Platform && Dimensions && Platform.OS !== 'web') {
+ // apply style for native
+ if (breakpoints[key] < Dimensions.get('window').width) {
+ addClassname({
+ body: {
+ [``]: {
+ [keyProperty]: valueProperty,
+ },
+ },
+ });
+ }
+ } else {
+ // apply style for web
+ const valueNormalized = normalizeUnitPixel(
+ keyProperty,
+ valueProperty,
+ isWeb
+ );
+ const body = {
+ [`${key}:${convertKeyToCss(keyProperty)}-[${valueNormalized}]`]:
+ {
+ [keyProperty]: valueNormalized,
+ },
+ };
+ addClassname({
+ wrapper: (str) =>
+ `@media (min-width: ${breakpointsValue}) { ${str} }`,
+ body,
+ });
+
+ if (props && typeof window !== 'undefined') {
+ if (breakpoints[key] < Dimensions.get('window').width) {
+ addClassname({ body });
+ }
+ }
+ }
+ });
+ }
+ });
+ },
+ };
+};
diff --git a/packages/styled/src/plugins/PseudoClass/PseudoClass.ts b/packages/styled/src/plugins/PseudoClass/PseudoClass.ts
new file mode 100644
index 00000000..17097adf
--- /dev/null
+++ b/packages/styled/src/plugins/PseudoClass/PseudoClass.ts
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+import type { CrossedstyleValues, Plugin } from '../../types';
+import { convertKeyToCss, normalizeUnitPixel } from './../utils';
+
+export interface CrossedPseudoClassProps {
+ focus?: true | false;
+ hover?: true | false;
+ active?: true | false;
+}
+
+export interface CrossedPseudoClassPlugin {
+ ':focus'?: CrossedstyleValues;
+ ':hover'?: CrossedstyleValues;
+ ':active'?: CrossedstyleValues;
+}
+
+export const PseudoClassPlugin: Plugin = {
+ name: 'PseudoClassPlugin',
+ test: '^:(hover|active|focus)$',
+ apply: ({ styles, key: ctxKey, addClassname, props, isWeb }) => {
+ const pseudoClass = ctxKey.replace(/:/i, '');
+ Object.entries(styles).forEach(([key, value]) => {
+ const valueNormalized = normalizeUnitPixel(key, value, isWeb);
+ if (isWeb) {
+ addClassname({
+ suffix: `:${pseudoClass}`,
+ body: {
+ [`${pseudoClass}:${convertKeyToCss(key)}-[${valueNormalized}]`]: {
+ [key]: valueNormalized,
+ },
+ },
+ });
+ }
+ if (props?.[pseudoClass] || !props) {
+ addClassname({
+ body: {
+ [`${convertKeyToCss(key)}-[${valueNormalized}]`]: {
+ [key]: valueNormalized,
+ },
+ },
+ });
+ }
+ }, {});
+ },
+};
diff --git a/packages/styled/src/plugins/PseudoClass/index.ts b/packages/styled/src/plugins/PseudoClass/index.ts
new file mode 100644
index 00000000..3acf9a5b
--- /dev/null
+++ b/packages/styled/src/plugins/PseudoClass/index.ts
@@ -0,0 +1,9 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+export * from './PseudoClass';
+export { useInteraction } from './useInteraction';
diff --git a/packages/styled/src/plugins/PseudoClass/useInteraction.ts b/packages/styled/src/plugins/PseudoClass/useInteraction.ts
new file mode 100644
index 00000000..9e7b63b8
--- /dev/null
+++ b/packages/styled/src/plugins/PseudoClass/useInteraction.ts
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+import { composeEventHandlers, useUncontrolled } from '@crossed/core';
+import { useCallback, useMemo, useTransition } from 'react';
+import type { PressableProps } from 'react-native';
+
+type PropsOverWrite = 'onPressIn' | 'onPressOut' | 'onHoverIn' | 'onHoverOut';
+export const useInteraction = (
+ props?: Pick & {
+ active?: boolean;
+ hover?: boolean;
+ }
+) => {
+ const [active, setActive] = useUncontrolled({
+ value: props?.active,
+ defaultValue: false,
+ });
+ const [hover, setHover] = useUncontrolled({
+ value: props?.hover,
+ defaultValue: false,
+ });
+ const [, setTransition] = useTransition();
+ const onPressIn = useCallback(
+ composeEventHandlers(props?.onPressIn || undefined, () => {
+ setTransition(() => {
+ if (!active) {
+ setActive(true);
+ }
+ });
+ }),
+ [active, setActive, props?.onPressIn]
+ );
+ const onPressOut = useCallback(
+ composeEventHandlers(props?.onPressOut || undefined, () => {
+ setTransition(() => {
+ if (active) {
+ setActive(false);
+ }
+ });
+ }),
+ [active, setActive, props?.onPressOut]
+ );
+
+ const onHoverIn = useCallback(
+ composeEventHandlers(props?.onHoverIn || undefined, () => {
+ setTransition(() => {
+ if (!hover) {
+ setHover(true);
+ }
+ });
+ }),
+ [hover, setHover, props?.onHoverIn]
+ );
+ const onHoverOut = useCallback(
+ composeEventHandlers(props?.onHoverOut || undefined, () => {
+ setTransition(() => {
+ if (!hover) {
+ setHover(false);
+ }
+ });
+ }),
+ [hover, setHover, props?.onHoverOut]
+ );
+
+ return useMemo(
+ () => ({
+ state: { active, hover },
+ props: { ...props, onPressIn, onPressOut, onHoverIn, onHoverOut },
+ }),
+ [props, active, hover, onPressIn, onPressOut, onHoverIn, onHoverOut]
+ );
+};
diff --git a/packages/styled/src/plugins/PseudoClass/useInteraction.web.ts b/packages/styled/src/plugins/PseudoClass/useInteraction.web.ts
new file mode 100644
index 00000000..a9cd5ffc
--- /dev/null
+++ b/packages/styled/src/plugins/PseudoClass/useInteraction.web.ts
@@ -0,0 +1,8 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+export const useInteraction = () => ({});
diff --git a/packages/styled/src/plugins/Variants.ts b/packages/styled/src/plugins/Variants.ts
new file mode 100644
index 00000000..61a30629
--- /dev/null
+++ b/packages/styled/src/plugins/Variants.ts
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+import { Registry } from '../Registry';
+import type { Plugin, StyleSheet } from '../types';
+
+interface Variants
+ extends Record>> {}
+
+export interface CrossedVariantsPlugin {
+ variants?: Variants;
+}
+
+type HasBooleanVariants = T extends 'true'
+ ? true
+ : T extends 'false'
+ ? true
+ : false;
+
+type AllKeys = T extends any ? keyof T : never;
+// eslint-disable-next-line no-unused-vars
+type PickType> = T extends { [k in K]?: any }
+ ? T[K]
+ : undefined;
+type Merge = {
+ [k in AllKeys]: PickType;
+};
+
+export interface CrossedVariantsPluginProps<
+ V extends Variants | undefined,
+ MV = Merge
+> {
+ variants?: {
+ [key in keyof MV]?: HasBooleanVariants extends false
+ ? keyof MV[key]
+ : keyof MV[key] | boolean;
+ };
+}
+
+export const VariantsPlugin: Plugin = {
+ name: 'VariantsPlugin',
+ test: '^variants$',
+ apply: ({ styles, addClassname, props, isWeb }) => {
+ Object.entries(styles).forEach(([variantName, variantValues]) => {
+ if (props && !props.variants?.[variantName]) {
+ return;
+ }
+ Object.entries(variantValues).forEach(([variantValue, style]) => {
+ if (
+ props &&
+ props.variants?.[variantName] &&
+ props.variants?.[variantName].toString() !== variantValue
+ ) {
+ return;
+ }
+ Registry.apply(() => style, {
+ isWeb,
+ props,
+ addClassname,
+ });
+ });
+ });
+ },
+};
diff --git a/packages/styled/src/plugins/Web.ts b/packages/styled/src/plugins/Web.ts
new file mode 100644
index 00000000..dcb2f059
--- /dev/null
+++ b/packages/styled/src/plugins/Web.ts
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+import type { CrossedstyleValues, Plugin } from '../types';
+import { convertKeyToCss, normalizeUnitPixel } from './utils';
+
+export interface CrossedWebPlugin {
+ web?: CrossedstyleValues;
+}
+
+export const WebPlugin: Plugin = {
+ name: 'WebPlugin',
+ test: '^web$',
+ apply: ({ addClassname, styles, isWeb }) => {
+ if (isWeb) {
+ addClassname({
+ body: Object.entries(styles).reduce>(
+ (acc, [key, value]) => {
+ const valueNormalized = normalizeUnitPixel(key, value, isWeb);
+ acc[`${convertKeyToCss(key)}-[${valueNormalized}]`] = {
+ [key]: valueNormalized,
+ };
+ return acc;
+ },
+ {}
+ ),
+ });
+ }
+ },
+};
diff --git a/packages/styled/src/plugins/index.ts b/packages/styled/src/plugins/index.ts
new file mode 100644
index 00000000..61304ff4
--- /dev/null
+++ b/packages/styled/src/plugins/index.ts
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+export { BasePlugin, type CrossedBasePlugin } from './Base';
+export {
+ MediaQueriesPlugin,
+ type CrossedMediaQueriesPlugin,
+} from './MediaQueries';
+export {
+ PseudoClassPlugin,
+ useInteraction,
+ type CrossedPseudoClassPlugin,
+ type CrossedPseudoClassProps,
+} from './PseudoClass';
+export { WebPlugin, type CrossedWebPlugin } from './Web';
+export {
+ VariantsPlugin,
+ type CrossedVariantsPlugin,
+ type CrossedVariantsPluginProps,
+} from './Variants';
+export * from './utils';
diff --git a/packages/styled/src/plugins/utils.ts b/packages/styled/src/plugins/utils.ts
new file mode 100644
index 00000000..7e3031c2
--- /dev/null
+++ b/packages/styled/src/plugins/utils.ts
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+export const convertToPx = [
+ 'lineHeight',
+ 'margin',
+ 'marginTop',
+ 'marginBottom',
+ 'marginLeft',
+ 'marginRight',
+ 'marginHorizontal',
+ 'marginVertical',
+ 'fontSize',
+ 'borderWidth',
+ 'borderBottomWidth',
+ 'borderTopWidth',
+ 'borderLeftWidth',
+ 'borderRightWidth',
+ 'borderRadius',
+ 'padding',
+ 'paddingTop',
+ 'paddingBottom',
+ 'paddingLeft',
+ 'paddingRight',
+ 'paddingHorizontal',
+ 'paddingVertical',
+ 'gap',
+ 'width',
+ 'height',
+];
+
+export const normalizeUnitPixel = (key: string, value: any, isWeb?: boolean) =>
+ isWeb && convertToPx.includes(key) && typeof value === 'number'
+ ? `${value}px`
+ : value;
+
+export const convertKeyToCss = (key: string) =>
+ key
+ .split(/(?=[A-Z])/)
+ .join('-')
+ .toLowerCase();
diff --git a/packages/styled/src/setTheme/index.ts b/packages/styled/src/setTheme/index.ts
new file mode 100644
index 00000000..3efb4498
--- /dev/null
+++ b/packages/styled/src/setTheme/index.ts
@@ -0,0 +1,8 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+export * from './setTheme';
diff --git a/packages/styled/src/setTheme/setTheme.ts b/packages/styled/src/setTheme/setTheme.ts
new file mode 100644
index 00000000..3223b995
--- /dev/null
+++ b/packages/styled/src/setTheme/setTheme.ts
@@ -0,0 +1,10 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+import type { SetTheme } from './types';
+
+export const setTheme: SetTheme = () => {};
diff --git a/packages/styled/src/setTheme/setTheme.web.ts b/packages/styled/src/setTheme/setTheme.web.ts
new file mode 100644
index 00000000..b6999a9f
--- /dev/null
+++ b/packages/styled/src/setTheme/setTheme.web.ts
@@ -0,0 +1,15 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+import type { SetTheme } from './types';
+
+export const setTheme: SetTheme = (old, theme) => {
+ if (typeof window !== 'undefined') {
+ window.document.documentElement.classList.remove(old);
+ window.document.documentElement.classList.add(theme);
+ }
+};
diff --git a/packages/styled/src/setTheme/types.ts b/packages/styled/src/setTheme/types.ts
new file mode 100644
index 00000000..01253c2d
--- /dev/null
+++ b/packages/styled/src/setTheme/types.ts
@@ -0,0 +1,8 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+export type SetTheme = (_old: string, _theme: string) => void;
diff --git a/packages/styled/src/styled.tsx b/packages/styled/src/styled.tsx
deleted file mode 100644
index 3c4a06af..00000000
--- a/packages/styled/src/styled.tsx
+++ /dev/null
@@ -1,138 +0,0 @@
-/**
- * Copyright (c) Paymium.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root of this projects source tree.
- */
-
-import { ReturnExtract, extract } from './extract';
-import { forwardRef, memo } from 'react';
-import { createStyleSheet, useStyles } from 'react-native-unistyles';
-import { withStaticProperties } from '@crossed/core/src/withStaticProperties';
-import type {
- ComponentLocal,
- ExtractStyle,
- ExtractVariant,
- UnistylesTheme,
- UnistylesValuesExtends,
-} from './types';
-import { useLogic } from './useLogic';
-import { useSignals } from '@preact/signals-react/runtime';
-import { Slot } from './Slot';
-import { parseStyle } from './parseStyle';
-import { Platform } from 'react-native';
-
-export type StyledOptions = { debug?: boolean; name?: string };
-
-export const styled = <
- P extends Record,
- S extends
- | Partial
- | ((_theme: UnistylesTheme) => Partial)
->(
- Comp: ComponentLocal,
- style: S,
- { debug: debugStyled, name }: StyledOptions = {}
-) => {
- // eslint-disable-next-line no-console
- const log = (...args: any[]) => console.log(`styled - "${name}" -`, ...args);
-
- debugStyled && log(`create styled component `);
-
- const styleSheet = (debug?: boolean) =>
- createStyleSheet((e) => {
- const styleParam = (
- typeof style === 'function' ? style(e) : style
- ) as ExtractStyle;
- const resultExtract = extract(styleParam);
- debug && log('after extrac ', resultExtract);
- return resultExtract;
- }) as unknown as S;
- return withStaticProperties(
- memo(
- forwardRef(
- (
- params: P &
- ExtractVariant & {
- hovered?: boolean;
- active?: boolean;
- focus?: boolean;
- debug?: boolean;
- asChild?: any;
- },
- ref: any
- ) => {
- const { debug, asChild, ...props } = params;
-
- const isDebug = debugStyled || debug;
-
- useSignals();
-
- const { styles } = useStyles(
- styleSheet(isDebug) as any,
- props as any
- );
-
- const { styles: old } = useStyles(
- ((Comp as any).styleSheet || {}) as any,
- props as any
- );
- const l = useLogic({
- name,
- props,
- debug: isDebug,
- styles: styles as any,
- });
- const t = useLogic({
- name: (Comp as any).displayName,
- props,
- debug: isDebug,
- styles: old as any,
- });
-
- isDebug && log(`render`);
- const { active, hovered, focus, ...otherProps } = props;
- const propsTmp = {
- ...(otherProps as any),
- ref: ref,
- ...l.actions,
- style:
- Platform.OS === 'web'
- ? parseStyle({
- ...t.styles.value,
- ...l.styles.value,
- })
- : {
- ...t.styles.value,
- ...l.styles.value,
- },
- };
- return asChild ? : ;
- }
- )
- ),
- {
- styleSheet: (e: UnistylesTheme) => {
- const st = styleSheet(debugStyled);
- const styleSheetExtends =
- 'styleSheet' in Comp ? Comp.styleSheet(e) : undefined;
- let stTmp: ReturnExtract;
- if (typeof st === 'function') {
- stTmp = st(e) as ReturnExtract;
- } else {
- stTmp = st as ReturnExtract;
- }
- return {
- ...styleSheetExtends,
- ...stTmp,
- base: { ...(styleSheetExtends || {}).base, ...stTmp.base },
- active: { ...(styleSheetExtends || {}).base, ...stTmp.active },
- focus: { ...(styleSheetExtends || {}).focus, ...stTmp.focus },
- hover: { ...(styleSheetExtends || {}).hover, ...stTmp.hover },
- };
- },
- name,
- displayName: name,
- }
- );
-};
diff --git a/packages/styled/src/types.ts b/packages/styled/src/types.ts
index f38cf9b3..9f408e30 100644
--- a/packages/styled/src/types.ts
+++ b/packages/styled/src/types.ts
@@ -5,90 +5,119 @@
* LICENSE file in the root of this projects source tree.
*/
-import type { ReactComponentWithRef } from '@crossed/core';
-import type {
- ComponentType,
- ForwardRefExoticComponent,
- PropsWithChildren,
- ReactNode,
-} from 'react';
-// import type { UnistylesTheme } from 'react-native-unistyles/lib/typescript/src/types';
-import type { UnistylesValues } from 'react-native-unistyles/lib/typescript/src/types/stylesheet';
-
-import type { UnistylesTheme } from 'react-native-unistyles/lib/typescript/src/types';
-import type { ReturnExtract } from './extract';
-
-export type Children = Omit
& {
- children?: ((_p: P) => ReactNode) | ReactNode;
-};
+import type { ImageStyle, TextStyle, ViewStyle } from 'react-native';
-export { UnistylesValues, UnistylesTheme };
-export type UnistylesValuesExtends = Omit &
- StateUnistylesValues &
- Variants & { name?: string };
+type NestedKeys = 'shadowOffset' | 'transform' | 'textShadowOffset';
-export type ExtraStyle = {
- extraStyle: (
- _props: P,
- _state: { hover: boolean; active: boolean; focus: boolean }
- ) => Omit;
-};
-type Variants = {
- variants?: {
- [variantName: string]: {
- [variant: string]: Omit;
- };
- };
+export type CrossedstyleView = Omit;
+export type CrossedstyleText = Omit;
+export type CrossedstyleImage = Omit;
+
+export type AllAvailableStyles = CrossedstyleView &
+ CrossedstyleText &
+ CrossedstyleImage;
+
+export interface CrossedstyleTheme {}
+
+export type AllAvailableKeys = keyof AllAvailableStyles;
+
+export type CrossedstyleValues = {
+ [propName in AllAvailableKeys]?: AllAvailableStyles[propName];
};
-export type ExtractStyle = T extends (_theme: UnistylesTheme) => infer R
- ? R
- : T;
-
-// export type ExtractVariantNames = T extends (...args: any) => infer R
-// ? ExtractVariantKeys
-// : ExtractVariantKeys;
-export type ExtractVariant = T extends (..._args: any[]) => infer R
- ? R extends { variants: infer V }
- ? { [key in keyof V]?: ExtractSubVariantKeys }
- : {}
- : T extends { variants: infer V }
- ? { [key in keyof V]?: ExtractSubVariantKeys }
- : {};
-type ExtractSubVariantKeys = T extends object
- ? keyof Omit | undefined
- : never;
-
-export type State =
- // | 'checked'
- // | 'readOnly'
- // | 'required'
- // | 'invalid'
- | 'focus'
- // | 'focusVisible'
- | 'hover'
- // | 'pressed'
- | 'active';
-// | 'loading'
-// | 'disabled';
-
-type StateUnistylesValues = {
- [key in State as `${key}:`]?: UnistylesValues;
+export interface StyleSheet {}
+
+export type CreateStylesParams<
+ K extends string,
+ S extends StyleSheet = StyleSheet
+> = (_theme: T) => Record;
+
+export type ExtractForProps> =
+ S extends CrossedMethods>
+ ? CrossedPropsExtended
+ : never;
+
+export type PluginContext = {
+ /**
+ * Key detected
+ */
+ key: keyof S;
+ /**
+ * style correspond of test key
+ */
+ styles: S[keyof S];
+
+ /**
+ * isWeb is true when plugin loaded by @crossed/loader
+ */
+ isWeb?: boolean;
+
+ /**
+ * props of component, only on runtime
+ */
+ props?: any;
+ /**
+ * Callback function for add className and style object
+ * @returns {void}
+ */
+ addClassname: (_params: {
+ /**
+ * Add suffix on className
+ * @returns {string} string to add as suffix
+ */
+ suffix?: string;
+ /**
+ * Add prefix on className
+ * @returns {string} string to add as prefix
+ */
+ prefix?: string;
+ wrapper?: (_str: string) => string;
+ body: Record;
+ }) => void;
};
-export type ExtractUnistylesValues = {
- // eslint-disable-next-line no-unused-vars
- [key in State]: UnistylesValues;
+export interface Themes {}
+
+// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
+export interface CrossedPropsExtended {
+ className?: string;
+ style?: Record;
+}
+
+export type CrossedMethods> = {
+ style: (_p?: P) => { style: Record };
+ className: (_p?: P) => { className: string };
+ rnw: (_p?: P) => { style: Record };
};
-export type StyledComponent =
- ComponentType
& {
- styleSheet: (_p: UnistylesTheme) => ReturnExtract;
- };
-
-export type ComponentLocal
> =
- | StyledComponent
- | ComponentType
- | ForwardRefExoticComponent
- | ReactComponentWithRef
- | (new (_props: P) => any);
+export type Plugin = {
+ /**
+ * Test of key, if true, apply plugin
+ */
+ test: string;
+
+ name: string;
+
+ /**
+ * init plugin
+ * @returns
+ */
+ init?: (_context: Omit>, 'key' | 'styles'>) => void;
+
+ /**
+ * apply transform on detected object
+ * @returns
+ */
+ apply?: (_context: PluginContext>) => void;
+
+ /**
+ * add utils added in parsing
+ * @returns
+ */
+ utils?: () => Record;
+ /**
+ * driver to use
+ * @returns
+ */
+ // useDriver?: () => any;
+};
diff --git a/packages/styled/src/unistyles.ts b/packages/styled/src/unistyles.ts
deleted file mode 100644
index 6cbb8a74..00000000
--- a/packages/styled/src/unistyles.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * Copyright (c) Paymium.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root of this projects source tree.
- */
-
-export {
- AndroidContentSizeCategory,
- IOSContentSizeCategory,
- ScreenOrientation,
- type UnistylesBreakpoints,
- type UnistylesPlugin,
- UnistylesRegistry,
- UnistylesRuntime,
- type UnistylesThemes,
- createStyleSheet,
- mq,
- useInitialTheme,
- useStyles,
-} from 'react-native-unistyles';
diff --git a/packages/styled/src/useLogic.ts b/packages/styled/src/useLogic.ts
deleted file mode 100644
index 767fa5ed..00000000
--- a/packages/styled/src/useLogic.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-/**
- * Copyright (c) Paymium.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root of this projects source tree.
- */
-
-import type { ReturnExtract } from './extract';
-import { useActive } from './hooks/useActive';
-import { useHover } from './hooks/useHover';
-import { effect, signal } from '@preact/signals-react';
-import { useFocus } from './hooks/useFocus';
-
-export const useLogic = >({
- name,
- props,
- debug,
- styles,
-}: {
- name?: string;
- props: P;
- styles: Partial;
- debug?: boolean;
-}) => {
- const log = (...args: any[]) =>
- // eslint-disable-next-line no-console
- console.log(`useLogic - "${name}" -`, ...args);
- const stylesComputed = signal(styles);
- const styleToRender = signal({});
-
- const { hovered, actions: actionsHover } = useHover(props);
- const { active, actions: actionsActive } = useActive(props);
- const { focus, actions: actionsFocus } = useFocus(props);
-
- effect(() => {
- const hoverStyle = hovered.value && stylesComputed.value.hover;
- const focusStyle = focus.value && stylesComputed.value.focus;
- const activeStyle = active.value && stylesComputed.value.active;
- const { extraStyle: extraStyleBase, ...base } =
- (stylesComputed.value.base as any) || {};
- const extraStyle =
- extraStyleBase?.(props, {
- focus: focus.value,
- active: active.value,
- hover: hovered.value,
- }) || {};
-
- const stylesUnify = {
- ...(base && Object.keys(base).length > 0 ? base : undefined),
- ...(focusStyle && Object.keys(focusStyle).length > 0
- ? focusStyle
- : undefined),
- ...(hoverStyle && Object.keys(hoverStyle).length > 0
- ? hoverStyle
- : undefined),
- ...(activeStyle && Object.keys(activeStyle).length > 0
- ? activeStyle
- : undefined),
- ...(extraStyle && Object.keys(extraStyle).length > 0
- ? extraStyle
- : undefined),
- ...(Array.isArray(props.style)
- ? props.style.reduce((acc, t) => ({ ...acc, ...t }), {})
- : props.style),
- };
- if (JSON.stringify(styleToRender) !== JSON.stringify(stylesUnify)) {
- debug &&
- log(
- 'reconciliate styles whith state',
- {
- isHover: hovered.value,
- isActive: active.value,
- isFocus: focus.value,
- },
- stylesUnify,
- stylesComputed.value
- );
- styleToRender.value = stylesUnify;
- }
- });
-
- debug && log('return value');
- return {
- styles: styleToRender,
- actions: {
- ...actionsActive,
- ...actionsHover,
- ...actionsFocus,
- },
- };
-};
diff --git a/packages/styled/src/useTheme.ts b/packages/styled/src/useTheme.ts
new file mode 100644
index 00000000..0552803f
--- /dev/null
+++ b/packages/styled/src/useTheme.ts
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+'use client';
+import { useEffect, useState, useTransition } from 'react';
+import { Registry } from './Registry';
+
+export const useTheme = () => {
+ const [theme, setTheme] = useState(Registry.getTheme());
+ const [, setTransition] = useTransition();
+ useEffect(() => {
+ const unsubscribe = Registry.subscribe(() => {
+ setTransition(() => setTheme(Registry.getTheme()));
+ });
+ return unsubscribe;
+ }, [setTheme, setTransition]);
+ return theme;
+};
diff --git a/packages/styled/tsconfig.build.json b/packages/styled/tsconfig.build.json
deleted file mode 100644
index 4c53934e..00000000
--- a/packages/styled/tsconfig.build.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
- "include": ["./src"],
- "exclude": ["node_modules"],
- "compilerOptions": {
- "baseUrl": ".",
- "outDir": "./lib/typescript",
- "ignoreDeprecations": "5.0",
- "emitDeclarationOnly": true,
- "declaration": true,
- "allowUnreachableCode": false,
- "allowUnusedLabels": true,
- "esModuleInterop": true,
- "importsNotUsedAsValues": "error",
- "forceConsistentCasingInFileNames": true,
- "jsx": "react-jsx",
- "lib": ["esnext", "dom"],
- "module": "esnext",
- "moduleResolution": "node",
- "noFallthroughCasesInSwitch": true,
- "noImplicitReturns": true,
- "noImplicitUseStrict": false,
- "noStrictGenericChecks": false,
- "noUnusedLocals": false,
- "noUnusedParameters": true,
- "resolveJsonModule": true,
- "skipLibCheck": true,
- "strict": true,
- "target": "esnext"
- }
-}
diff --git a/packages/styled/tsconfig.json b/packages/styled/tsconfig.json
index 3410d28b..1dd1ab41 100644
--- a/packages/styled/tsconfig.json
+++ b/packages/styled/tsconfig.json
@@ -1,38 +1,20 @@
{
- "include": ["./src", "./jest-setup.ts", "__tests__"],
- "exclude": ["node_modules", "example"],
+ "include": ["./src"],
+ "exclude": ["node_modules"],
"compilerOptions": {
"outDir": "./lib/typescript",
- "ignoreDeprecations": "5.0",
- "noEmit": false,
- "allowSyntheticDefaultImports": true,
- "declaration": true,
- "allowUnreachableCode": false,
- "allowUnusedLabels": true,
- "esModuleInterop": true,
- "isolatedModules": false,
- "composite": true,
- "importsNotUsedAsValues": "error",
- "forceConsistentCasingInFileNames": true,
- "jsx": "react-jsx",
- "lib": ["esnext", "dom"],
- "module": "esnext",
+ "noImplicitAny": true,
+ "module": "es6",
+ "lib": ["es2017", "dom"],
+ "target": "es5",
+ "jsx": "react",
"allowJs": true,
"moduleResolution": "node",
- "noFallthroughCasesInSwitch": true,
- "noImplicitReturns": true,
- "noImplicitUseStrict": false,
- "noStrictGenericChecks": false,
- "noUnusedLocals": false,
- "noUnusedParameters": true,
- "resolveJsonModule": true,
+ "declaration": true,
"skipLibCheck": true,
- "strict": true,
- "target": "esnext",
- "paths": {
- "@crossed/core": [
- "./node_modules/@crossed/core/src"
- ]
- }
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
+ "allowSyntheticDefaultImports": true,
+ "verbatimModuleSyntax": false,
}
}
diff --git a/packages/styled/tsconfig.test.json b/packages/styled/tsconfig.test.json
new file mode 100644
index 00000000..4d4cdb38
--- /dev/null
+++ b/packages/styled/tsconfig.test.json
@@ -0,0 +1,8 @@
+{
+ "include": ["./src", "**/__tests__"],
+ "exclude": ["node_modules"],
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "verbatimModuleSyntax": false
+ }
+}
diff --git a/packages/test/package.json b/packages/test/package.json
index bb31e400..1a9f7fee 100644
--- a/packages/test/package.json
+++ b/packages/test/package.json
@@ -25,19 +25,18 @@
"@testing-library/user-event": "^14.5.1",
"@types/jest": "^29.5.0",
"@types/node": "18.11.18",
- "@types/react": "^18.2.21",
+ "@types/react": "^18.2.64",
"@types/react-dom": "18.2.7",
- "@types/react-native": "^0.72.5",
"jest": "^29.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "react-native": "^0.72.5",
+ "react-native": "^0.73.5",
"react-native-reanimated": "^3.5.1",
"react-native-web": "^0.19.10",
"ts-node": "10.8.1",
"ts-node-dev": "^2.0.0",
"tsconfig": "*",
- "typescript": "5.1.6"
+ "typescript": "5.4.2"
},
"files": [
"lib/",
@@ -47,6 +46,7 @@
"@babel/plugin-transform-flow-strip-types": "^7.23.3",
"@jest/globals": "^29.7.0",
"@preact/signals-react-transform": "^0.3.0",
+ "@testing-library/react-hooks": "^8.0.1",
"babel-preset-flow": "^6.23.0",
"ts-jest": "^29.1.1"
}
diff --git a/packages/test/src/index.ts b/packages/test/src/index.ts
index a7afb121..5130b10e 100644
--- a/packages/test/src/index.ts
+++ b/packages/test/src/index.ts
@@ -7,4 +7,5 @@
export * from '@testing-library/react';
export * from '@testing-library/user-event';
+export { renderHook, act } from '@testing-library/react-hooks';
export * from './mocks/Signal';
diff --git a/packages/test/src/jest.config.ts b/packages/test/src/jest.config.ts
index 7f326efa..519bf137 100644
--- a/packages/test/src/jest.config.ts
+++ b/packages/test/src/jest.config.ts
@@ -6,7 +6,7 @@
*/
import type { Config } from '@jest/types';
-import path from 'path';
+import * as path from 'path';
import { readFileSync } from 'fs';
let localSetup: boolean = false;
@@ -21,6 +21,7 @@ try {
const config: Config.InitialOptions = {
// preset: 'react-native',
rootDir: process.cwd(),
+ extensionsToTreatAsEsm: ['.ts', '.tsx', '.mts'],
transform: {
'^.+\\.(j)sx?$': [
'babel-jest',
@@ -43,6 +44,8 @@ const config: Config.InitialOptions = {
'^react-native-reanimated$':
'/node_modules/react-native-reanimated/mock',
},
+ coveragePathIgnorePatterns: ['/node_modules/', '__tests__'],
+ testRegex: '\\.spec\\.[jt]sx?$',
transformIgnorePatterns: [
// 'node_modules/(?!((.pnpm/)?((jest-)?react-native|@react-native(-community)?))|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|tamagui|@tamagui/.*|@crossed/.*|react-native-reanimated|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg)',
'node_modules/(?!((.pnpm/)?(@preact)))',
diff --git a/packages/test/tsconfig.test.json b/packages/test/tsconfig.test.json
new file mode 100644
index 00000000..25f325a9
--- /dev/null
+++ b/packages/test/tsconfig.test.json
@@ -0,0 +1,6 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "verbatimModuleSyntax": false
+ }
+}
diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md
index 9171940e..b79c8f53 100644
--- a/packages/ui/CHANGELOG.md
+++ b/packages/ui/CHANGELOG.md
@@ -1,5 +1,65 @@
# @crossed/ui
+## 1.0.0-beta.5
+
+### Patch Changes
+
+- Updated dependencies [63171a9]
+ - @crossed/styled@0.14.0-beta.5
+ - @crossed/unicons@1.0.0-beta.5
+
+## 1.0.0-beta.4
+
+### Patch Changes
+
+- Updated dependencies
+ - @crossed/styled@0.14.0-beta.4
+ - @crossed/core@0.8.1-beta.1
+ - @crossed/unicons@1.0.0-beta.4
+ - @crossed/primitive@1.7.1-beta.1
+
+## 1.0.0-beta.3
+
+### Patch Changes
+
+- Updated dependencies [7a7a589]
+ - @crossed/styled@0.14.0-beta.3
+ - @crossed/unicons@1.0.0-beta.3
+
+## 1.0.0-beta.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @crossed/core@0.8.1-beta.0
+ - @crossed/primitive@1.7.1-beta.0
+ - @crossed/styled@0.14.0-beta.2
+ - @crossed/unicons@1.0.0-beta.2
+
+## 1.0.0-beta.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @crossed/styled@0.14.0-beta.1
+ - @crossed/unicons@1.0.0-beta.1
+
+## 1.0.0-beta.0
+
+### Minor Changes
+
+- 7b48a2b: feat(theme): add theme colors and utils for apply variants based on colors
+
+### Patch Changes
+
+- b7f88a7: fix(Tabs): missing active state when tab is selected
+- Updated dependencies [d9f9372]
+- Updated dependencies [31e0003]
+- Updated dependencies [c7e44dc]
+- Updated dependencies [8917db4]
+ - @crossed/styled@0.14.0-beta.0
+ - @crossed/unicons@1.0.0-beta.0
+
## 0.9.0
### Minor Changes
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 9db5f052..c87c2f45 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -1,7 +1,7 @@
{
"name": "@crossed/ui",
"description": "A universal & performant styling library for React Native, Next.js & React",
- "version": "0.9.0",
+ "version": "1.0.0-beta.5",
"keywords": [
"React Native",
"React",
@@ -30,36 +30,66 @@
"react-native": "src/index",
"source": "src/index",
"typings": "lib/typescript/index.d.ts",
+ "exports": {
+ ".": {
+ "require": "./lib/commonjs/index.js",
+ "import": "./lib/module/index.js",
+ "default": "./lib/module/index.js",
+ "types": "./lib/typescript/index.d.ts"
+ },
+ "./theme": {
+ "require": "./lib/commonjs/theme/theme.js",
+ "import": "./lib/module/theme/theme.js",
+ "default": "./lib/module/theme/theme.js",
+ "types": "./lib/typescript/theme/theme.d.ts"
+ }
+ },
+ "typesVersions": {
+ "*": {
+ "index": [
+ "lib/typescript/index.d.ts"
+ ],
+ "theme": [
+ "lib/typescript/theme/theme.d.ts"
+ ]
+ }
+ },
"scripts": {
"release": "release-it",
"clean": "rm -rf lib",
- "watch": "crossed-build --watch --project-path ./tsconfig.build.json",
- "build": "crossed-build --project-path ./tsconfig.build.json",
+ "watch": "crossed-build --watch",
+ "build": "crossed-build",
"dev:web": "cd example/native && yarn web --clear",
"type-check": "tsc --noEmit"
},
+ "peerDependencies": {
+ "@crossed/core": "0.8.1-beta.1",
+ "@crossed/build": "*",
+ "@crossed/primitive": "1.7.1-beta.1",
+ "@crossed/styled": "workspace:^",
+ "@crossed/unicons": "1.0.0-beta.5"
+ },
"devDependencies": {
+ "@crossed/core": "0.8.1-beta.1",
"@crossed/build": "*",
+ "@crossed/primitive": "1.7.1-beta.1",
+ "@crossed/styled": "workspace:^",
"@crossed/test": "*",
+ "@crossed/unicons": "1.0.0-beta.5",
"@jest/globals": "^29.7.0",
"@jest/types": "28.1.1",
"@testing-library/jest-dom": "^6.1.3",
"@types/jest": "^29.5.0",
- "@types/react": "^18.2.21",
- "@types/react-native": "^0.72.5",
+ "@types/react": "^18.2.64",
"babel-plugin-transform-remove-console": "^6.9.4",
"jest": "^29.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "react-native": "^0.72.5",
+ "react-native": "^0.73.5",
"react-native-web": "^0.19.10",
"tsconfig": "*"
},
"dependencies": {
- "@crossed/core": "*",
- "@crossed/primitive": "*",
- "@crossed/styled": "*",
- "@crossed/unicons": "*",
"@expo/html-elements": "^0.5.1",
"@floating-ui/react": "^0.25.1",
"@gorhom/portal": "^1.0.14",
@@ -69,7 +99,7 @@
"inline-style-prefixer": "^6.0.1",
"normalize-css-color": "^1.0.2",
"react-aria": "^3.27.0",
- "react-native-svg": "^13.10.0",
+ "react-native-svg": "^15.1.0",
"react-native-unistyles": "^2.0.0"
},
"react-native-builder-bob": {
diff --git a/packages/ui/src/Provider.tsx b/packages/ui/src/Provider.tsx
index fd5bad93..d5a5e727 100644
--- a/packages/ui/src/Provider.tsx
+++ b/packages/ui/src/Provider.tsx
@@ -6,18 +6,11 @@
*/
'use client';
-import { setup } from './theme';
-setup();
-import { Registry } from '@crossed/styled';
// export { CrossedTheme, useCrossedTheme } from '@crossed/styled';
import { PortalProvider } from '@gorhom/portal';
import type { PropsWithChildren } from 'react';
export const CrossedUIProvider = ({ children }: PropsWithChildren) => {
- return (
-
- {children}
-
- );
+ return {children} ;
};
diff --git a/packages/ui/src/disclosure/Tabs.tsx b/packages/ui/src/disclosure/Tabs.tsx
index 720361ea..7d16a393 100644
--- a/packages/ui/src/disclosure/Tabs.tsx
+++ b/packages/ui/src/disclosure/Tabs.tsx
@@ -8,116 +8,125 @@
'use client';
import {
- UseUncontrolledInput,
+ type UseUncontrolledInput,
composeEventHandlers,
createScope,
useUncontrolled,
- withDefaultProps,
withStaticProperties,
} from '@crossed/core';
import { useCallback, type PropsWithChildren } from 'react';
import { Button, type ButtonProps } from '../forms/Button';
-import { XBox } from '../layout/XBox';
-import { styled } from '@crossed/styled';
-import { YBox, YBoxProps } from '../layout/YBox';
+import { XBox, type XBoxProps } from '../layout/XBox';
+import { YBox, type YBoxProps } from '../layout/YBox';
+import { createStyles } from '@crossed/styled';
+
+const useStyles = createStyles((t) => ({
+ list: {
+ base: {
+ borderBottomWidth: 1,
+ borderStyle: 'solid',
+ borderColor: t.colors.neutral,
+ paddingBottom: t.space.xs,
+ },
+ },
+}));
type TabsContext = Pick & {
value: string | number;
setValue: (_value: string | number) => void;
};
+export const createTabs = () => {
+ const [TabsProvider, useTabsContext] = createScope(
+ {} as TabsContext
+ );
-const [TabsProvider, useTabsContext] = createScope(
- {} as TabsContext
-);
-
-const TabsRoot = ({
- children,
- value: valueProps,
- defaultValue,
- finalValue,
- onChange,
- variant = 'ghost',
- size = 'sm',
- ...props
-}: PropsWithChildren<
- Pick &
- UseUncontrolledInput &
- YBoxProps
->) => {
- const [value, setValue] = useUncontrolled({
+ const TabsRoot = ({
+ children,
value: valueProps,
defaultValue,
finalValue,
onChange,
- });
-
- return (
-
-
- {children}
-
-
- );
-};
-
-const List = withDefaultProps(
- styled(XBox, (t) => ({
- borderBottomWidth: 1,
- borderColor: t.colors.neutral,
- paddingBottom: t.space.xs,
- })),
- { space: 'xs' }
-);
-
-const Panels = ({ children }: PropsWithChildren) => {
- return children;
-};
-const TabImpl = withStaticProperties(
- ({
- value: valueProps,
+ variant = 'ghost',
+ size = 'sm',
...props
- }: ButtonProps & Pick) => {
- const { variant, setValue, size, value } = useTabsContext();
-
- const onPress = useCallback(
- composeEventHandlers(() => {
- setValue(valueProps);
- }, props.onPress),
- [props.onPress, setValue]
- );
+ }: PropsWithChildren<
+ Partial> &
+ UseUncontrolledInput &
+ YBoxProps
+ >) => {
+ const [value, setValue] = useUncontrolled({
+ value: valueProps,
+ defaultValue,
+ finalValue,
+ onChange,
+ });
return (
-
+ value={value}
+ variant={variant}
+ setValue={setValue}
+ >
+
+ {children}
+
+
);
- },
- { Text: Button.Text }
-);
-const Panel = ({
- value: valueProps,
- children,
-}: PropsWithChildren<{ value: string | number }>) => {
- const { value } = useTabsContext();
- return valueProps === value ? children : null;
+ };
+
+ const List = (props: XBoxProps) => {
+ return ;
+ };
+
+ const Panels = ({ children }: PropsWithChildren) => {
+ return children;
+ };
+ const TabImpl = withStaticProperties(
+ ({
+ value: valueProps,
+ ...props
+ }: ButtonProps & Pick) => {
+ const { variant, setValue, size, value } = useTabsContext();
+
+ const onPress = useCallback(
+ composeEventHandlers(() => {
+ setValue(valueProps);
+ }, props.onPress),
+ [props.onPress, setValue]
+ );
+
+ return (
+
+ );
+ },
+ { Text: Button.Text }
+ );
+ const Panel = ({
+ value: valueProps,
+ children,
+ }: PropsWithChildren<{ value: string | number }>) => {
+ const { value } = useTabsContext();
+ return valueProps === value ? children : null;
+ };
+
+ return withStaticProperties(TabsRoot, {
+ List,
+ Panels,
+ Tab: TabImpl,
+ Panel,
+ });
};
-const Tabs = withStaticProperties(TabsRoot, {
- List,
- Panels,
- Tab: TabImpl,
- Panel,
-});
+const Tabs = createTabs();
const { List: TabList, Panels: TabPanels, Tab, Panel: TabPanel } = Tabs;
+const { Text: TabText } = Tab;
-export { Tabs, TabList, TabPanels, Tab, TabPanel };
+export { Tabs, TabList, TabPanels, Tab, TabText, TabPanel };
diff --git a/packages/ui/src/display/Card.tsx b/packages/ui/src/display/Card.tsx
index ad233e65..bf6e0a00 100644
--- a/packages/ui/src/display/Card.tsx
+++ b/packages/ui/src/display/Card.tsx
@@ -5,49 +5,67 @@
* LICENSE file in the root of this projects source tree.
*/
-import {
- type GetProps,
- withDefaultProps,
- withStaticProperties,
-} from '@crossed/core';
-import { styled } from '@crossed/styled';
-import { YBox } from '../layout/YBox';
-import { Text } from '../typography/Text';
-
-const CardRoot = styled(YBox, (t) => ({
- padding: t.space.md,
- borderRadius: t.space.xs,
- backgroundColor: t.utils.shadeColor(t.colors.background, 25),
- variants: {
- role: {
- link: {
- 'hover:': {
- backgroundColor: t.utils.shadeColor(t.colors.background, 30),
+import { type GetProps, withStaticProperties } from '@crossed/core';
+import { YBox, type YBoxProps } from '../layout/YBox';
+import { Text, type TextProps } from '../typography/Text';
+import { createStyles, type ExtractForProps } from '@crossed/styled';
+import { forwardRef } from 'react';
+
+const useCard = createStyles(
+ (t) =>
+ ({
+ root: {
+ base: {
+ padding: t.space.md,
+ borderRadius: t.space.xs,
+ backgroundColor: t.colors.neutral,
},
- 'active:': {
- backgroundColor: t.utils.shadeColor(t.colors.background, 20),
+ variants: {
+ role: {
+ link: {
+ ':hover': {
+ backgroundColor: t.colors.backgroundStrong,
+ },
+ ':active': {
+ backgroundColor: t.colors.backgroundStrong,
+ },
+ },
+ },
},
},
- },
- },
-}));
+ title: { base: { alignSelf: 'stretch' } },
+ description: { base: { alignSelf: 'stretch' } },
+ } as const)
+);
+
+type Variants = ExtractForProps;
+
+type CardProps = YBoxProps & Variants['variants'];
-const Title = withDefaultProps(styled(Text, { alignSelf: 'stretch' }), {
- size: 'lg',
+const CardRoot = forwardRef(({ role, ...props }: CardProps, ref: any) => {
+ return (
+
+ );
});
-const Description = withDefaultProps(
- styled(Text, { alignSelf: 'stretch' }),
- {}
-);
+const Title = (props: TextProps) => {
+ return ;
+};
+const Description = (props: TextProps) => {
+ return ;
+};
const Card = withStaticProperties(CardRoot, {
Title,
Description,
});
const { Title: CardTitle, Description: CardDescription } = Card;
-type CardProps = GetProps;
type CardTitleProps = GetProps;
type CardDescriptionProps = GetProps;
diff --git a/packages/ui/src/display/Code.tsx b/packages/ui/src/display/Code.tsx
index e9ad6668..9bbfc7f3 100644
--- a/packages/ui/src/display/Code.tsx
+++ b/packages/ui/src/display/Code.tsx
@@ -5,20 +5,26 @@
* LICENSE file in the root of this projects source tree.
*/
-import { GetProps, UnistylesRuntime, styled } from '@crossed/styled';
-import { Text } from '../typography/Text';
+import { createStyles } from '@crossed/styled';
+import { Text, type TextProps } from '../typography/Text';
-const Code = styled(Text, (t) => ({
- backgroundColor: t.colors.neutral,
- paddingVertical: 1,
- paddingHorizontal: t.space.xs,
- borderWidth: 1,
- borderColor: t.utils.shadeColor(
- t.colors.neutral,
- (UnistylesRuntime.themeName === 'dark' ? 1 : -1) * 50
- ),
- borderRadius: 4,
+const useCode = createStyles((t) => ({
+ root: {
+ base: {
+ backgroundColor: t.colors.neutral,
+ paddingVertical: 1,
+ paddingHorizontal: t.space.xs,
+ borderWidth: 1,
+ borderStyle: 'solid',
+ borderRadius: 4,
+ },
+ },
}));
-type CodeProps = GetProps;
+
+type CodeProps = TextProps;
+
+const Code = (props: CodeProps) => {
+ return ;
+};
export { Code, type CodeProps };
diff --git a/packages/ui/src/display/Kbd.tsx b/packages/ui/src/display/Kbd.tsx
index 3778a263..58e33c1b 100644
--- a/packages/ui/src/display/Kbd.tsx
+++ b/packages/ui/src/display/Kbd.tsx
@@ -5,20 +5,26 @@
* LICENSE file in the root of this projects source tree.
*/
-import { GetProps, UnistylesRuntime, styled } from '@crossed/styled';
-import { Text } from '../typography/Text';
+import { createStyles } from '@crossed/styled';
+import { Text, type TextProps } from '../typography/Text';
-const Kbd = styled(Text, (t) => ({
- backgroundColor: t.colors.neutral,
- paddingVertical: 1,
- paddingHorizontal: t.space.xs,
- borderWidth: 1,
- borderColor: t.utils.shadeColor(
- t.colors.neutral,
- (UnistylesRuntime.themeName === 'dark' ? 1 : -1) * 50
- ),
- borderRadius: 4,
+const useKbd = createStyles((t) => ({
+ root: {
+ base: {
+ backgroundColor: t.colors.neutral,
+ paddingVertical: 1,
+ paddingHorizontal: t.space.xs,
+ borderWidth: 1,
+ borderStyle: 'solid',
+ borderRadius: 4,
+ },
+ },
}));
-type KbdProps = GetProps;
+
+type KbdProps = TextProps;
+
+const Kbd = (props: KbdProps) => {
+ return ;
+};
export { Kbd, type KbdProps };
diff --git a/packages/ui/src/display/MenuList.tsx b/packages/ui/src/display/MenuList.tsx
index d12fe55e..e9b2c7c5 100644
--- a/packages/ui/src/display/MenuList.tsx
+++ b/packages/ui/src/display/MenuList.tsx
@@ -7,54 +7,54 @@
'use client';
import { createList } from '@crossed/primitive';
-import { styled, useStyles } from '@crossed/styled';
-import { Text, TextProps } from '../typography/Text';
-import { YBox } from '../layout/YBox';
+import { createStyles } from '@crossed/styled';
+import { Text, type TextProps } from '../typography/Text';
+import { YBox, type YBoxProps } from '../layout/YBox';
import { Divider as D } from '../layout/Divider';
-import { Button, ButtonProps } from '../forms/Button';
-import { GetProps, createScope } from '@crossed/core';
+import { Button, useButton, type ButtonProps } from '../forms/Button';
+import { type GetProps, createScope } from '@crossed/core';
import { forwardRef, memo } from 'react';
-type ButtonVariantProps = Pick;
-const MenuRoot = styled(YBox, () => ({
- alignItems: 'stretch',
+const useMenuList = createStyles(() => ({
+ root: {
+ base: {
+ alignItems: 'stretch',
+ },
+ },
}));
-type MenuRootProps = GetProps;
+type ButtonVariantProps = Partial>;
-const Divider = styled(D, {});
-const Item = forwardRef((props: ButtonProps, ref) => {
- const context = useVariantContext();
- return ;
+const MenuRoot = forwardRef((props: MenuRootProps, ref: any) => {
+ return ;
});
-const Label = forwardRef((props: TextProps & ButtonVariantProps, ref) => {
- const context = useVariantContext();
- const { styles } = useStyles(Button.styleSheet as any, {
- ...context,
- ...props,
- });
+type MenuRootProps = YBoxProps;
+
+const Divider = D;
+const Item = Button;
+
+const Label = forwardRef((props: TextProps & ButtonVariantProps, ref: any) => {
+ const variants = useVariantContext();
return (
);
});
-const Title = styled(Button.Text, {});
-const SubTitle = styled(Button.Text, {});
+const Title = Button.Text;
+const SubTitle = Button.Text;
type ContextVariant = ButtonVariantProps;
-const [ProviderVariant, useVariantContext] = createScope({});
+const [ProviderVariant, useVariantContext] = createScope(
+ {} as ContextVariant
+);
const MenuList = createList({
Root: memo(
- forwardRef((props: MenuRootProps & ButtonVariantProps, ref) => (
+ forwardRef((props: MenuRootProps & ButtonVariantProps, ref: any) => (
) => ,
- () => ({
- width: '100%',
- borderCollapse: 'collapse',
- borderWidth: 0,
- tableLayout: 'fixed',
- })
-);
-
-export const THead = styled(
- ({ children, ...props }: HTMLProps) => {
- const newChildren = useMemo(
- () =>
- Children.map(children, (c) => {
- if (isValidElement(c) && (c.type as any).id === 'Table.Tr') {
- return cloneElement(c, {
- ...c.props,
- style: { borderTopWidth: 0 },
- });
- }
- return c;
- }),
- [children]
- );
- return {newChildren} ;
+const useTable = createStyles((t) => ({
+ table: {
+ base: {
+ width: '100%',
+ borderCollapse: 'collapse',
+ borderWidth: 0,
+ tableLayout: 'fixed',
+ },
},
- (t) => ({
- borderWidth: 0,
- borderBottomWidth: 1,
- borderStyle: 'solid',
- borderColor: t.colors.neutral,
- backgroundColor: t.utils.hexToRgbA(
- t.utils.shadeColor(t.colors.neutral, 5),
- 0.5
- ),
- })
-);
-export const TBody = styled(
- ({ children, ...props }: HTMLProps) => {
- const newChild =
- Array.isArray(children) && children.length > 0 ? (
- children
- ) : (
-
-
- -
-
-
- -
-
-
-
- -
-
-
-
- );
- return {newChild} ;
+ thead: {
+ base: {
+ borderTopWidth: 0,
+ borderLeftWidth: 0,
+ borderRightWidth: 0,
+ borderBottomWidth: 1,
+ borderStyle: 'solid',
+ borderColor: t.colors.neutral,
+ backgroundColor: t.colors.backgroundSoft,
+ },
},
- {}
-);
-export const Tr = styled(
- (props: HTMLProps) => ,
- (t) => ({
- borderWidth: 0,
- borderTopWidth: 1,
- borderStyle: 'solid',
- borderColor: t.colors.neutral,
- })
-);
+ tbody: { base: {} },
+ tr: {
+ base: {
+ borderTopWidth: 1,
+ borderLeftWidth: 0,
+ borderRightWidth: 0,
+ borderBottomWidth: 0,
+ borderStyle: 'solid',
+ borderColor: t.colors.neutral,
+ },
+ },
+ td: {
+ base: {
+ padding: t.space.sm,
+ // variants: {
+ // textAlign,
+ // },
+ },
+ },
+ th: { base: { textAlign: 'left', padding: t.space.sm } },
+}));
+
+export const Table = (props: HTMLProps) => {
+ return ;
+};
+
+export const THead = (props: HTMLProps) => {
+ return ;
+};
+
+export const TBody = ({
+ children,
+ ...props
+}: HTMLProps) => {
+ const newChild =
+ (Array.isArray(children) && children.length > 0) || children ? (
+ children
+ ) : (
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ );
+ return (
+
+ {newChild}
+
+ );
+};
+export const Tr = (props: HTMLProps) => {
+ return ;
+};
(Tr as any).id = 'Table.Tr';
-export const Td = styled(
- (props: HTMLProps) => ,
- (t) => ({
- padding: t.space.sm,
- variants: {
- textAlign,
- },
- })
-);
-export const Th = styled(
- (props: HTMLProps) => ,
- (t) => ({ textAlign: 'left', padding: t.space.sm })
-);
+export const Td = (props: HTMLProps) => {
+ return ;
+};
+export const Th = (props: HTMLProps) => {
+ return ;
+};
diff --git a/packages/ui/src/feedback/Alert.tsx b/packages/ui/src/feedback/Alert.tsx
index f103f8f4..29c5a02f 100644
--- a/packages/ui/src/feedback/Alert.tsx
+++ b/packages/ui/src/feedback/Alert.tsx
@@ -7,27 +7,45 @@
'use client';
import { withDefaultProps, withStaticProperties } from '@crossed/core';
-import { styled } from '@crossed/styled';
import { Text } from '../typography/Text';
-import { XBox } from '../layout/XBox';
-
-const Container = withDefaultProps(
- styled(XBox, (t) => ({
- padding: t.space.md,
- borderRadius: 4,
- alignItems: 'flex-start',
- variants: {
- status: {
- error: { backgroundColor: t.colors.error },
- success: { backgroundColor: t.colors.success },
- warning: { backgroundColor: t.colors.warning },
- info: { backgroundColor: t.colors.info },
+import { XBox, type XBoxProps } from '../layout/XBox';
+import { createStyles, type ExtractForProps } from '@crossed/styled';
+
+const useAlert = createStyles(
+ (t) =>
+ ({
+ container: {
+ base: {
+ padding: t.space.md,
+ borderRadius: 4,
+ alignItems: 'center',
+ },
+ variants: {
+ status: {
+ error: { base: { backgroundColor: t.colors.error } },
+ success: { base: { backgroundColor: t.colors.success } },
+ warning: { base: { backgroundColor: t.colors.warning } },
+ info: { base: { backgroundColor: t.colors.info } },
+ },
+ },
},
- },
- })),
- { space: undefined }
+ } as const)
);
+type Variant = ExtractForProps;
+
+type ContainerProps = XBoxProps & Variant['variants'];
+
+const Container = ({ status, ...props }: ContainerProps) => {
+ return (
+
+ );
+};
+
const Icon = () => {};
const Title = withDefaultProps(Text, { weight: 'bold', size: 'lg' });
diff --git a/packages/ui/src/forms/Button.tsx b/packages/ui/src/forms/Button.tsx
index f266e638..4e86d167 100644
--- a/packages/ui/src/forms/Button.tsx
+++ b/packages/ui/src/forms/Button.tsx
@@ -7,95 +7,107 @@
'use client';
-import { styled } from '@crossed/styled';
+import { createStyles, type ExtractForProps } from '@crossed/styled';
import { createButton } from '@crossed/primitive';
-import { Pressable, View } from 'react-native';
+import { Pressable, type PressableProps } from 'react-native';
import { Text as TextUi } from '../typography/Text';
-import {
- GetProps,
- withDefaultProps,
- withStaticProperties,
-} from '@crossed/core';
+import { type GetProps, withDefaultProps } from '@crossed/core';
+import { XBox } from '../layout/XBox';
+import { Box } from '../layout/Box';
+import { forwardRef } from 'react';
-const Group = styled(View, () => ({}));
-const Root = styled(Pressable, (t) => ({
- alignItems: 'center',
- justifyContent: 'center',
- flexDirection: 'row',
- gap: t.space.sm,
- borderRadius: t.space.xs,
- borderWidth: 1,
- variants: {
- size: {
- xs: { paddingHorizontal: t.space.xs, height: t.size.xs },
- sm: { paddingHorizontal: t.space.sm, height: t.size.sm },
- default: { paddingHorizontal: t.space.md, height: t.size.md },
- lg: { paddingHorizontal: t.space.lg, height: t.size.lg },
- xl: { paddingHorizontal: t.space.xl, height: t.size.xl },
- } as const,
- color: t.utils.createVariants('backgroundColor', t),
- variant: {
- default: {},
- ghost: {},
- outlined: {},
+export const useButton = createStyles((t) => ({
+ button: {
+ base: {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ flexDirection: 'row',
+ gap: t.space.sm,
+ borderRadius: t.space.xs,
+ borderWidth: 1,
+ borderStyle: 'solid',
+ boxSizing: 'border-box',
},
- },
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore: missing type of extraStyle function
- extraStyle: ({ color, variant }, state) => {
- const select = (c: string) => {
- const color = t.utils.select(
- {
- active: t.utils.hexToRgbA(t.utils.shadeColor(c, 25), 0.5),
- hover: t.utils.hexToRgbA(t.utils.shadeColor(c, 5), 0.5),
- base: 'transparent',
+ variants: {
+ size: {
+ xs: { base: { paddingHorizontal: t.space.xs, height: t.size.xs } },
+ sm: { base: { paddingHorizontal: t.space.sm, height: t.size.sm } },
+ md: { base: { paddingHorizontal: t.space.md, height: t.size.md } },
+ lg: { base: { paddingHorizontal: t.space.lg, height: t.size.lg } },
+ xl: { base: { paddingHorizontal: t.space.xl, height: t.size.xl } },
+ },
+ // color: t.utils.createVariants('backgroundColor', t),
+ variant: {
+ default: {
+ 'base': {
+ borderColor: t.colors.neutral,
+ backgroundColor: t.colors.neutral,
+ },
+ ':hover': {
+ borderColor: t.colors.neutral,
+ backgroundColor: t.colors.neutral,
+ },
+ ':active': {
+ borderColor: t.colors.neutral,
+ backgroundColor: t.colors.neutral,
+ },
},
- state
- );
- if (variant === 'outlined') {
- return {
- borderColor: c,
- backgroundColor: color,
- };
- } else if (variant === 'ghost') {
- return {
- borderColor: 'transparent',
- backgroundColor: color,
- };
- }
- const colorDefault = t.utils.select(
- {
- active: t.utils.shadeColor(c, -15),
- hover: t.utils.shadeColor(c, 15),
- base: c,
+ ghost: {
+ 'base': {
+ borderColor: 'transparent',
+ backgroundColor: 'transparent',
+ },
+ ':hover': {
+ backgroundColor: t.colors.neutral,
+ },
+ ':active': {
+ backgroundColor: t.colors.neutral,
+ },
},
- state
- );
- return {
- borderColor: colorDefault,
- backgroundColor: colorDefault,
- };
- };
-
- if (color && color !== 'neutral') {
- return select((t.colors as any)[color]);
- }
- return select(t.colors.neutral);
- },
+ outlined: {
+ 'base': {
+ borderColor: t.colors.neutral,
+ backgroundColor: 'transparent',
+ },
+ ':hover': {
+ backgroundColor: t.colors.neutral,
+ },
+ ':active': {
+ backgroundColor: t.colors.neutral,
+ },
+ },
+ },
+ },
+ } as const,
}));
+
+const Group = XBox;
+
+type VariantButton = ExtractForProps;
+type RootProps = PressableProps &
+ VariantButton['variants'] &
+ Omit;
+const Root = forwardRef(({ size, variant, ...props }: RootProps, ref: any) => {
+ return (
+
+ );
+});
+
const Text = withDefaultProps(TextUi, { weight: 'semibold' });
-const Element = styled(View, {});
+const Element = Box;
-const Button = withStaticProperties(
- createButton({
- Group,
- Root,
- Text,
- Element,
- }),
- { styleSheet: Root.styleSheet }
-);
+const Button = createButton({
+ Group,
+ Root: withDefaultProps(Root, { size: 'md', variant: 'default' }),
+ Text,
+ Element,
+});
const { Text: ButtonText, Element: ButtonElement } = Button;
diff --git a/packages/ui/src/forms/Input.tsx b/packages/ui/src/forms/Input.tsx
index a8331cb4..2d888707 100644
--- a/packages/ui/src/forms/Input.tsx
+++ b/packages/ui/src/forms/Input.tsx
@@ -24,11 +24,11 @@
// import { Pressable, TextInput } from 'react-native';
import { createInput } from '@crossed/primitive';
-import { UnistylesRuntime, styled, useStyles } from '@crossed/styled';
-import { TextInput, TextInputProps } from 'react-native';
-import { YBox } from '../layout/YBox';
-import { XBox, XBoxProps } from '../layout/XBox';
+import { TextInput, type TextInputProps } from 'react-native';
+import { YBox, type YBoxProps } from '../layout/YBox';
+import { XBox } from '../layout/XBox';
import { forwardRef } from 'react';
+import { createStyles } from '@crossed/styled';
// const [Provider, useContext] = createScope<{
// size?: keyof typeof sizeVariants;
@@ -254,76 +254,45 @@ import { forwardRef } from 'react';
// Content: InputContent,
// });
-const Addon = styled(YBox, {});
-const Element = styled(YBox, {
- position: 'absolute',
- right: 0,
- top: 0,
- bottom: 0,
- color: 'white',
- alignItems: 'center',
- justifyContent: 'center',
-});
-const Group = styled(
- (props: XBoxProps) => {
- // const toto = props.children.reduce((acc, e) => {
- // if (e.type.displayName === 'Input.Element') {
- // console.log(e)
- // acc = e.props.style.width;
- // }
- // return acc;
- // }, undefined);
- return ;
+const useInput = createStyles((t) => ({
+ element: {
+ base: {
+ position: 'absolute',
+ right: 0,
+ top: 0,
+ bottom: 0,
+ color: 'white',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ },
+ root: {
+ base: {
+ color: t.colors.default,
+ backgroundColor: t.colors.background,
+ borderWidth: 1,
+ borderStyle: 'solid',
+ // borderColor: t.utils.shadeColor(
+ // t.colors.neutral,
+ // UnistylesRuntime.themeName === 'dark' ? 100 : -100
+ // ),
+ borderRadius: 4,
+ paddingVertical: t.space.xs,
+ paddingHorizontal: t.space.sm,
+ textAlign: 'right',
+ },
},
- () => ({
- // backgroundColor: t.utils.shadeColor(t.colors.background, 50),
- // borderWidth: 1,
- // borderColor: t.utils.shadeColor(
- // t.colors.neutral,
- // UnistylesRuntime.themeName === 'dark' ? 100 : -100
- // ),
- // borderRadius: 4,
- // paddingVertical: t.space.xs,
- // paddingHorizontal: t.space.sm,
- })
-);
+}));
-const InputRoot = styled(
- forwardRef((props: TextInputProps, ref: any) => {
- const { theme } = useStyles();
- // const { toto } = useInputProvider();
- // console.log(toto, props.style);
- return (
-
- );
- }),
- (t) => ({
- color: t.colors.default,
- backgroundColor: t.utils.shadeColor(t.colors.background, 50),
- borderWidth: 1,
- borderColor: t.utils.shadeColor(
- t.colors.neutral,
- UnistylesRuntime.themeName === 'dark' ? 100 : -100
- ),
- borderRadius: 4,
- paddingVertical: t.space.xs,
- paddingHorizontal: t.space.sm,
- textAlign: 'right',
- })
-);
+const Addon = YBox;
+const Element = (props: YBoxProps) => {
+ return ;
+};
+const Group = XBox;
+
+const InputRoot = forwardRef((props: TextInputProps, ref: any) => {
+ return ;
+});
const Input = createInput({
Addon,
diff --git a/packages/ui/src/forms/Select/index.tsx b/packages/ui/src/forms/Select/index.tsx
index 16b36b74..bbb22f8b 100644
--- a/packages/ui/src/forms/Select/index.tsx
+++ b/packages/ui/src/forms/Select/index.tsx
@@ -34,27 +34,31 @@
// import { Box, BoxProps } from '../../layout/Box';
import {
- UseUncontrolledInput,
+ type UseUncontrolledInput,
composeEventHandlers,
createScope,
useUncontrolled,
withStaticProperties,
} from '@crossed/core';
-import { styled } from '@crossed/styled';
import {
useCallback,
memo,
- ReactNode,
+ type ReactNode,
useRef,
Children,
isValidElement,
- MutableRefObject,
+ type MutableRefObject,
} from 'react';
-import { Button, ButtonProps } from '../Button';
-import { XBox, XBoxProps } from '../../layout/XBox';
-import { MenuItemProps, MenuList, MenuListProps } from '../../display/MenuList';
+import { Button, type ButtonProps } from '../Button';
+import { XBox, type XBoxProps } from '../../layout/XBox';
+import {
+ type MenuItemProps,
+ MenuList,
+ type MenuListProps,
+} from '../../display/MenuList';
import { Portal } from '@gorhom/portal';
import type { LayoutRectangle } from 'react-native';
+import { createStyles } from '@crossed/styled';
// // type InputProps = GetProps;
@@ -505,7 +509,7 @@ const findChild = (
value: string | number
): ReactNode | undefined => {
if (!children) {
- return;
+ return undefined;
}
return typeof children === 'function'
? undefined
@@ -525,46 +529,64 @@ const findChild = (
}, undefined);
};
-const SelectRoot = styled(
- memo(
- ({
+const useSelect = createStyles((t) => ({
+ select: {
+ base: {
+ position: 'relative',
+ width: 'auto',
+ },
+ },
+ content: {
+ base: {
+ position: 'absolute',
+ // top: 15 + 40,
+ // left: 1253,
+ maxWidth: 'auto',
+ padding: t.space.xs,
+ zIndex: 100,
+ backgroundColor: t.colors.neutral,
+ borderRadius: 4,
+ },
+ },
+}));
+
+const SelectRoot = memo(
+ ({
+ value: valueProps,
+ defaultValue,
+ finalValue,
+ onChange,
+ variant,
+ children,
+ ...props
+ }: UseUncontrolledInput & XBoxProps & Pick) => {
+ const renderValue = useRef();
+ const triggerLayout = useRef();
+ const [value, setValue] = useUncontrolled({
value: valueProps,
defaultValue,
finalValue,
onChange,
- variant,
- ...props
- }: UseUncontrolledInput & XBoxProps & Pick) => {
- const renderValue = useRef();
- const triggerLayout = useRef();
- const [value, setValue] = useUncontrolled({
- value: valueProps,
- defaultValue,
- finalValue,
- onChange,
- });
- const [open, setOpen] = useUncontrolled({
- defaultValue: false,
- });
- renderValue.current = findChild(props.children, value) || 'rien trouvé';
- return (
-
-
-
- );
- }
- ),
- {
- position: 'relative',
- width: 'auto',
+ });
+ const [open, setOpen] = useUncontrolled({
+ defaultValue: false,
+ });
+ renderValue.current = findChild(children, value) || 'rien trouvé';
+ return (
+
+
+ {children}
+
+
+ );
}
);
@@ -584,8 +606,7 @@ const Trigger = withStaticProperties(
return (
{
+ onLayout={({ nativeEvent: { layout } }: any) => {
triggerLayout.current = layout;
}}
{...props}
@@ -596,67 +617,55 @@ const Trigger = withStaticProperties(
{ Text: Button.Text }
);
-const Content = styled(
- (props: MenuListProps) => {
- const all = useSelectProvider();
- const { top, height, left } = (all.triggerLayout.current as any) || {
- top: 0,
- height: 0,
- left: 0,
- };
- return (
-
-
- {all.open ? (
- <>
- ) => {
+ const all = useSelectProvider();
+ const { top, height, left } = (all.triggerLayout.current as any) || {
+ top: 0,
+ height: 0,
+ left: 0,
+ };
+ return (
+
+
+ {all.open ? (
+ <>
+
- >
- ) : null}
-
-
- );
- },
- (t) => ({
- position: 'absolute',
- top: 15 + 40,
- left: 1253,
- maxWidth: 'auto',
- padding: t.space.xs,
- zIndex: 100,
- backgroundColor: t.utils.shadeColor(t.colors.neutral, -25),
- borderRadius: 4,
- })
-);
+ position: 'absolute',
+ },
+ })}
+ />
+ >
+ ) : null}
+
+
+ );
+};
-// @ts-expect-error because id not exist in type
Content.id = 'Select.Content';
Content.displayName = 'Select.Content';
-const Option = styled(
- ({ value, ...props }: MenuItemProps & { value: string | number }) => {
- const { setOpen, setValue, value: valueGlobal } = useSelectProvider();
- return (
- {
- setOpen(false);
- setValue(value);
- }, props.onPress)}
- />
- );
- },
- { justifyContent: 'flex-start' }
-);
+const Option = ({
+ value,
+ ...props
+}: MenuItemProps & { value: string | number }) => {
+ const { setOpen, setValue, value: valueGlobal } = useSelectProvider();
+ return (
+ {
+ setOpen(false);
+ setValue(value);
+ }, props.onPress)}
+ />
+ );
+};
-// @ts-expect-error because id not exist in type
Option.id = 'Select.Option';
Option.displayName = 'Select.Option';
diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts
index a38799e8..620b9d0c 100644
--- a/packages/ui/src/index.ts
+++ b/packages/ui/src/index.ts
@@ -7,7 +7,6 @@
'use client';
// react-native-unistyles/lib/typescript/src/types/core
-import 'react-native-unistyles';
export * from './layout';
// export * from './variants';
export * from './typography';
diff --git a/packages/ui/src/layout/Box.ts b/packages/ui/src/layout/Box.ts
deleted file mode 100644
index 4429677d..00000000
--- a/packages/ui/src/layout/Box.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * Copyright (c) Paymium.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root of this projects source tree.
- */
-
-'use client';
-
-import { View } from 'react-native';
-import { styled } from '@crossed/styled';
-import type { GetProps } from '@crossed/core';
-
-export const Box = styled(View, (t) => ({
- display: 'flex',
- variants: {
- space: {
- xs: { gap: t.space.xs },
- sm: { gap: t.space.sm },
- md: { gap: t.space.md },
- lg: { gap: t.space.lg },
- xl: { gap: t.space.xl },
- },
- center: { true: { alignItems: 'center' } },
- },
-}));
-
-export type BoxProps = GetProps;
diff --git a/packages/ui/src/layout/Box.tsx b/packages/ui/src/layout/Box.tsx
new file mode 100644
index 00000000..2f3a5106
--- /dev/null
+++ b/packages/ui/src/layout/Box.tsx
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+import { View, type ViewProps } from 'react-native';
+import { createStyles, type ExtractForProps } from '@crossed/styled';
+import { forwardRef } from 'react';
+import type { GetProps } from '@crossed/core';
+
+const styleBox = createStyles(
+ (t) =>
+ ({
+ root: {
+ base: { display: 'flex' },
+ web: { boxSizing: 'border-box' },
+ variants: {
+ space: {
+ xs: { base: { gap: t.space.xs } },
+ sm: { base: { gap: t.space.sm } },
+ md: { base: { gap: t.space.md } },
+ lg: { base: { gap: t.space.lg } },
+ xl: { base: { gap: t.space.xl } },
+ },
+ center: { true: { base: { alignItems: 'center' } } },
+ },
+ },
+ } as const)
+);
+
+type Variant = ExtractForProps;
+
+type BoxPropsTmp = Pick &
+ Omit &
+ ViewProps;
+
+export const Box = forwardRef(
+ (
+ {
+ style,
+ className,
+ space,
+ center,
+ active,
+ hover,
+ focus,
+ ...props
+ }: BoxPropsTmp,
+ ref: any
+ ) => (
+
+ )
+);
+
+export type BoxProps = GetProps;
diff --git a/packages/ui/src/layout/Center.tsx b/packages/ui/src/layout/Center.tsx
index d8fef049..3016ce8e 100644
--- a/packages/ui/src/layout/Center.tsx
+++ b/packages/ui/src/layout/Center.tsx
@@ -5,10 +5,17 @@
* LICENSE file in the root of this projects source tree.
*/
-import { styled } from '@crossed/styled';
-import { YBox } from './YBox';
+import { createStyles } from '@crossed/styled';
+import { YBox, type YBoxProps } from './YBox';
-export const Center = styled(YBox, {
- justifyContent: 'center',
- alignItems: 'center',
-});
+export const useCenter = createStyles(() => ({
+ root: {
+ base: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ },
+}));
+export const Center = (props: YBoxProps) => {
+ return ;
+};
diff --git a/packages/ui/src/layout/Divider.ts b/packages/ui/src/layout/Divider.ts
deleted file mode 100644
index 4f34c68d..00000000
--- a/packages/ui/src/layout/Divider.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * Copyright (c) Paymium.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root of this projects source tree.
- */
-
-'use client';
-
-import { View } from 'react-native';
-import { styled } from '@crossed/styled';
-import { withDefaultProps } from '@crossed/core';
-
-export const Divider = withDefaultProps(
- styled(View, (t) => ({
- borderWidth: 0,
- borderColor: t.colors.neutral,
- variants: {
- direction: {
- vertical: {
- borderLeftWidth: 1,
- height: '100%',
- },
- horizontal: {
- borderTopWidth: 1,
- width: '100%',
- },
- },
- },
- })),
- { role: 'separator' }
-);
diff --git a/packages/ui/src/layout/Divider.tsx b/packages/ui/src/layout/Divider.tsx
new file mode 100644
index 00000000..bf509204
--- /dev/null
+++ b/packages/ui/src/layout/Divider.tsx
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+'use client';
+
+import { View, type ViewProps } from 'react-native';
+import { createStyles, type ExtractForProps } from '@crossed/styled';
+
+export const useDivider = createStyles(
+ (t) =>
+ ({
+ divider: {
+ base: {
+ borderWidth: 0,
+ borderStyle: 'solid',
+ borderColor: t.colors.neutral,
+ },
+ variants: {
+ direction: {
+ vertical: {
+ base: {
+ borderLeftWidth: 1,
+ height: '100%',
+ },
+ },
+ horizontal: {
+ base: {
+ borderTopWidth: 1,
+ width: '100%',
+ },
+ },
+ },
+ },
+ },
+ } as const)
+);
+
+type Variant = ExtractForProps;
+
+export type DividerProps = ViewProps & Variant['variants'];
+
+export const Divider = ({ direction, ...props }: DividerProps) => {
+ return (
+
+ );
+};
diff --git a/packages/ui/src/layout/XBox.ts b/packages/ui/src/layout/XBox.ts
deleted file mode 100644
index 7bbed420..00000000
--- a/packages/ui/src/layout/XBox.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * Copyright (c) Paymium.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root of this projects source tree.
- */
-
-'use client';
-
-import { styled } from '@crossed/styled';
-import type { GetProps } from '@crossed/core';
-import { Box } from './Box';
-
-export const XBox = styled(Box, () => ({
- display: 'flex',
- flexDirection: 'row',
- justifyContent: 'flex-start',
- width: '100%',
- flexBasis: 'auto',
- variants: {
- center: { true: { alignItems: 'center' } },
- },
-}));
-
-export type XBoxProps = GetProps;
diff --git a/packages/ui/src/layout/XBox.tsx b/packages/ui/src/layout/XBox.tsx
new file mode 100644
index 00000000..cecfeb9c
--- /dev/null
+++ b/packages/ui/src/layout/XBox.tsx
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+import { createStyles } from '@crossed/styled';
+import { Box, type BoxProps } from './Box';
+import { forwardRef } from 'react';
+
+export const useXBox = createStyles(
+ () =>
+ ({
+ root: {
+ base: {
+ display: 'flex',
+ flexDirection: 'row',
+ justifyContent: 'flex-start',
+ width: '100%',
+ flexBasis: 'auto',
+ },
+ },
+ } as const)
+);
+
+export type XBoxProps = BoxProps;
+
+export const XBox = forwardRef(({ center, ...props }: XBoxProps, ref: any) => {
+ return (
+
+ );
+});
diff --git a/packages/ui/src/layout/YBox.ts b/packages/ui/src/layout/YBox.ts
deleted file mode 100644
index 2acef960..00000000
--- a/packages/ui/src/layout/YBox.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- * Copyright (c) Paymium.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root of this projects source tree.
- */
-
-'use client';
-
-import { styled } from '@crossed/styled';
-import type { GetProps } from '@crossed/core';
-import { Box } from './Box';
-
-export const YBox = styled(Box, () => ({
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'flex-start',
- alignSelf: 'stretch',
- maxWidth: '100%',
-}));
-
-export type YBoxProps = GetProps;
diff --git a/packages/ui/src/layout/YBox.tsx b/packages/ui/src/layout/YBox.tsx
new file mode 100644
index 00000000..d6f642d3
--- /dev/null
+++ b/packages/ui/src/layout/YBox.tsx
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+'use client';
+
+import { createStyles } from '@crossed/styled';
+import { Box, type BoxProps } from './Box';
+import { forwardRef } from 'react';
+
+const useYBox = createStyles(
+ () =>
+ ({
+ root: {
+ base: {
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'flex-start',
+ alignSelf: 'stretch',
+ maxWidth: '100%',
+ },
+ },
+ } as const)
+);
+
+export type YBoxProps = BoxProps;
+
+export const YBox = forwardRef((props: YBoxProps, ref: any) => {
+ return ;
+});
diff --git a/packages/ui/src/theme/index.ts b/packages/ui/src/theme/index.ts
index 0827764c..abcc84ad 100644
--- a/packages/ui/src/theme/index.ts
+++ b/packages/ui/src/theme/index.ts
@@ -5,38 +5,43 @@
* LICENSE file in the root of this projects source tree.
*/
-import { UnistylesRegistry } from '@crossed/styled/unistyles';
-import { darkTheme, lightTheme } from './theme';
-import { breakpoints } from './breakpoints';
-import deepmerge from 'deepmerge';
+// import { Registry } from '@crossed/styled/registry';
+// import { darkTheme } from './theme';
+// import { breakpoints } from './breakpoints';
+// import deepmerge from 'deepmerge';
import type { Extends, Themes } from './types';
+// import type { CrossedBasePlugin } from '@crossed/styled/src/plugins/Base';
+// import type {
+// CrossedVariantsPlugin,
+// CrossedVariantsPluginProps,
+// } from '@crossed/styled/src/plugins/Variants';
+// import type { CrossedMediaQueriesPlugin } from '@crossed/styled/src/plugins/MediaQueries';
+// import type { CrossedPseudoClassPlugin } from '@crossed/styled/src/plugins/PseudoClass';
-type AppBreakpoints = typeof breakpoints;
+// type AppBreakpoints = typeof breakpoints;
-type AppThemes = {
- light: typeof lightTheme;
- dark: typeof darkTheme;
-};
+// type AppThemes = typeof darkTheme;
-declare module '@crossed/styled' {
- export interface UnistylesBreakpoints extends AppBreakpoints {}
- export interface UnistylesThemes extends AppThemes {}
-}
+// type AppThemes = {
+// light: typeof lightTheme;
+// dark: typeof darkTheme;
+// };
export const setup = ({
- themes,
- extends: extendsProps,
+ themes: _themes,
+ extends: _extendsProps,
}: { themes?: Themes; extends?: Extends } = {}) => {
- UnistylesRegistry.addBreakpoints(breakpoints)
- .addThemes(
- themes ??
- deepmerge((extendsProps?.themes as any) || {}, {
- light: lightTheme,
- dark: darkTheme,
- })
- )
- .addConfig({
- initialTheme: 'dark',
- // experimentalCSSMediaQueries: true
- });
+ // Registry.setTheme(darkTheme);
+ // Registry.addBreakpoints(breakpoints)
+ // .addThemes(
+ // themes ??
+ // deepmerge((extendsProps?.themes as any) || {}, {
+ // light: lightTheme,
+ // dark: darkTheme,
+ // })
+ // )
+ // .addConfig({
+ // initialTheme: 'dark',
+ // // experimentalCSSMediaQueries: true
+ // });
};
diff --git a/packages/ui/src/theme/theme.ts b/packages/ui/src/theme/theme.ts
index 1210e8e4..a84b29f0 100644
--- a/packages/ui/src/theme/theme.ts
+++ b/packages/ui/src/theme/theme.ts
@@ -6,13 +6,7 @@
*/
import { colorsDark, colorsLight } from './colors';
-import type {
- Colors,
- Entries,
- Theme,
- VariantBackgroundColor,
- VariantColor,
-} from './types';
+import type { Theme } from './types';
const space = {
xs: 4,
@@ -45,111 +39,20 @@ export const fontSize: Theme['fontSize'] = {
'5xl': 48,
};
-const utils: Theme['utils'] = {
- shadeColor: (col, amt) => {
- col = col.replace(/^#/, '');
- if (col.length === 3)
- col = col[0] + col[0] + col[1] + col[1] + col[2] + col[2];
-
- let [r, g, b]: any[] = col.match(/.{2}/g) || [];
-
- [r, g, b] = [
- parseInt(r, 16) + amt,
- parseInt(g, 16) + amt,
- parseInt(b, 16) + amt,
- ];
-
- if ((!r && r !== 0) || (!g && g !== 0) || (!b && b !== 0)) {
- throw new Error();
- }
-
- r = Math.max(Math.min(255, r), 0).toString(16);
- g = Math.max(Math.min(255, g), 0).toString(16);
- b = Math.max(Math.min(255, b), 0).toString(16);
-
- const rr = (r.length < 2 ? '0' : '') + r;
- const gg = (g.length < 2 ? '0' : '') + g;
- const bb = (b.length < 2 ? '0' : '') + b;
-
- return `#${rr}${gg}${bb}`;
- },
- hexToRgbA: (hex: string, alpha: number) => {
- const r = parseInt(hex.slice(1, 3), 16),
- g = parseInt(hex.slice(3, 5), 16),
- b = parseInt(hex.slice(5, 7), 16);
-
- if (alpha) {
- return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + alpha + ')';
- } else {
- return 'rgb(' + r + ', ' + g + ', ' + b + ')';
- }
- },
- select: ({ hover, active, focus, base }, state) => {
- return (
- (state.active
- ? active
- : state.hover
- ? hover
- : state.focus
- ? focus
- : base) || base
- );
- },
- createVariants: (type, t) => {
- if (type === 'color') {
- return (Object.entries(t.colors) as Entries).reduce(
- (acc, [key, value]) => {
- if (
- key !== 'background' &&
- key !== 'backgroundSoft' &&
- key !== 'backgroundStrong'
- ) {
- acc[key] = { color: value };
- }
- return acc;
- },
- {} as VariantColor
- ) as any;
- }
- if (type === 'backgroundColor') {
- return (
- Object.entries(t.colors) as Entries
- ).reduce((acc, [key, value]) => {
- if (
- key !== 'background' &&
- key !== 'backgroundSoft' &&
- key !== 'backgroundStrong' &&
- key !== 'black' &&
- key !== 'default' &&
- key !== 'white'
- ) {
- acc[key] = { backgroundColor: value };
- }
- return acc;
- }, {} as any);
- }
- throw new Error(`createVariants not allowed type "${type}"`);
- },
-};
-
export const lightTheme = {
colors: colorsLight,
- fontFamily:
- '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif',
+ fontFamily: 'Roboto',
space,
// padding,
size,
- utils,
fontSize,
} satisfies Theme;
export const darkTheme = {
colors: colorsDark,
- fontFamily:
- '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif',
+ fontFamily: 'Roboto',
space,
// padding,
size,
- utils,
fontSize,
-} satisfies Theme;
+} as const;
diff --git a/packages/ui/src/theme/types.ts b/packages/ui/src/theme/types.ts
index 2aa85f77..647b4fef 100644
--- a/packages/ui/src/theme/types.ts
+++ b/packages/ui/src/theme/types.ts
@@ -5,7 +5,7 @@
* LICENSE file in the root of this projects source tree.
*/
-import type { UnistylesThemes } from '@crossed/styled';
+import type { CrossedstyleTheme } from '@crossed/styled';
export type Entries = {
[K in keyof T]: [K, T[K]];
@@ -37,7 +37,7 @@ export type VariantColor = {
[key in keyof Omit<
Colors,
'background' | 'backgroundStrong' | 'backgroundSoft'
- >]: { color: Colors[key] };
+ >]: { base: { color: Colors[key] } };
};
export type VariantBackgroundColor = {
@@ -49,7 +49,7 @@ export type VariantBackgroundColor = {
| 'black'
| 'default'
| 'white'
- >]: { backgroundColor: Colors[key] };
+ >]: { base: { backgroundColor: Colors[key] } };
};
export type Theme = {
@@ -82,20 +82,8 @@ export type Theme = {
'4xl': number;
'5xl': number;
};
- utils: {
- shadeColor: (_col: string, _amt: number) => string;
- hexToRgbA: (_hex: string, _alpha: number) => string;
- select: (
- _p: { hover?: any; active?: any; focus?: any; base?: any },
- _state: { hover?: boolean; active?: boolean; focus?: boolean }
- ) => string;
- createVariants: {
- (_type: 'color', _theme: Theme): VariantColor;
- (_type: 'backgroundColor', _theme: Theme): VariantBackgroundColor;
- };
- };
};
-export type Themes = UnistylesThemes & {
+export type Themes = CrossedstyleTheme & {
light: Theme;
dark: Theme;
[key: string]: Theme;
diff --git a/packages/ui/src/type.ts b/packages/ui/src/type.ts
new file mode 100644
index 00000000..9eae5b9a
--- /dev/null
+++ b/packages/ui/src/type.ts
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) Paymium.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+import type {
+ CrossedBasePlugin,
+ CrossedMediaQueriesPlugin,
+ CrossedPseudoClassPlugin,
+ CrossedVariantsPlugin,
+ CrossedVariantsPluginProps,
+ CrossedPseudoClassProps,
+} from '@crossed/styled/plugins';
+import { darkTheme, lightTheme } from './theme/theme';
+import type { breakpoints } from './theme/breakpoints';
+
+const themes = { dark: darkTheme, light: lightTheme };
+type ThemesCustom = typeof themes;
+type Breakpoints = keyof typeof breakpoints;
+
+declare module '@crossed/styled' {
+ export interface Themes extends ThemesCustom {}
+
+ export interface StyleSheet
+ extends CrossedBasePlugin,
+ CrossedVariantsPlugin,
+ CrossedMediaQueriesPlugin,
+ CrossedPseudoClassPlugin {}
+
+ export interface CrossedPropsExtended
+ extends CrossedPseudoClassProps,
+ CrossedVariantsPluginProps {}
+}
diff --git a/packages/ui/src/typography/Anchor.tsx b/packages/ui/src/typography/Anchor.tsx
index 8d3ad325..a05835b4 100644
--- a/packages/ui/src/typography/Anchor.tsx
+++ b/packages/ui/src/typography/Anchor.tsx
@@ -5,26 +5,34 @@
* LICENSE file in the root of this projects source tree.
*/
-import { styled } from '@crossed/styled';
-import { Text } from './Text';
-import type { GetProps } from '@crossed/core';
-import { withDefaultProps } from '@crossed/core';
+import { createStyles } from '@crossed/styled';
+import { Text, type TextProps } from './Text';
+import { forwardRef } from 'react';
-export const Anchor = withDefaultProps(
- styled(
- Text,
- (t) => ({
- 'fontFamily': t.fontFamily,
- 'textDecorationLine': 'none',
- 'cursor': 'pointer',
- 'hover:': {
- textDecorationLine: 'underline',
- color: t.utils.shadeColor(t.colors.link, 45),
- },
- }),
- { name: 'Anchor' }
- ),
- { weight: 'medium', role: 'link', color: 'link' }
-);
+export const useAnchor = createStyles((t) => ({
+ anchor: {
+ 'base': {
+ fontFamily: t.fontFamily,
+ textDecorationLine: 'none',
+ cursor: 'pointer',
+ },
+ ':hover': {
+ textDecorationLine: 'underline',
+ color: t.colors.link,
+ },
+ },
+}));
-export type AnchorProps = GetProps;
+export type AnchorProps = TextProps;
+export const Anchor = forwardRef((props: AnchorProps, ref: any) => {
+ return (
+
+ );
+});
diff --git a/packages/ui/src/typography/Heading.tsx b/packages/ui/src/typography/Heading.tsx
index 4bd5077c..7d0adb01 100644
--- a/packages/ui/src/typography/Heading.tsx
+++ b/packages/ui/src/typography/Heading.tsx
@@ -7,8 +7,7 @@
'use client';
-import { styled } from '@crossed/styled';
-import { Text, TextProps } from './Text';
+import { Text, type TextProps } from './Text';
import { withDefaultProps } from '@crossed/core';
type HeadingProps = TextProps & { 'aria-level'?: number };
@@ -20,29 +19,23 @@ export const H1 = withDefaultProps(Text, {
'weight': 'bold',
});
-export const H2 = withDefaultProps(
- styled(Text, (t) => ({ marginTop: t.space.xl, marginBottom: t.space.md })),
- {
- 'role': 'heading',
- 'aria-level': 2,
- 'size': '4xl',
- 'weight': 'semibold',
- }
-);
+export const H2 = withDefaultProps(Text, {
+ 'role': 'heading',
+ 'aria-level': 2,
+ 'size': '4xl',
+ 'weight': 'semibold',
+});
export const H3 = withDefaultProps(Text, {
'role': 'heading',
'aria-level': 3,
'size': '3xl',
});
-export const H4 = withDefaultProps(
- styled(Text, { marginTop: 20 }),
- {
- 'role': 'heading',
- 'aria-level': 4,
- 'size': '2xl',
- }
-);
+export const H4 = withDefaultProps(Text, {
+ 'role': 'heading',
+ 'aria-level': 4,
+ 'size': '2xl',
+});
export const H5 = withDefaultProps(Text, {
'role': 'heading',
'aria-level': 5,
diff --git a/packages/ui/src/typography/I.tsx b/packages/ui/src/typography/I.tsx
index 6b22a716..a8db126d 100644
--- a/packages/ui/src/typography/I.tsx
+++ b/packages/ui/src/typography/I.tsx
@@ -5,12 +5,18 @@
* LICENSE file in the root of this projects source tree.
*/
-import { Text } from './Text';
-import { styled } from '@crossed/styled';
-import type { GetProps } from '@crossed/core';
+import { Text, type TextProps } from './Text';
+import { createStyles } from '@crossed/styled';
-export const I = styled(Text, {
- fontStyle: 'italic',
-});
+const useItalic = createStyles(() => ({
+ root: {
+ base: {
+ fontStyle: 'italic',
+ },
+ },
+}));
-export type IProps = GetProps;
+export type IProps = TextProps;
+export const I = (props: IProps) => {
+ return ;
+};
diff --git a/packages/ui/src/typography/List.tsx b/packages/ui/src/typography/List.tsx
index 6c7adf50..2af25c26 100644
--- a/packages/ui/src/typography/List.tsx
+++ b/packages/ui/src/typography/List.tsx
@@ -5,35 +5,40 @@
* LICENSE file in the root of this projects source tree.
*/
-import { styled, withDefaultProps } from '@crossed/styled';
-import { Text, TextProps } from './Text';
-import { forwardRef, memo } from 'react';
-import { XBox, XBoxProps } from '../layout/XBox';
-import { YBox } from '../layout/YBox';
+import { Text } from './Text';
+import { XBox, type XBoxProps } from '../layout/XBox';
+import { YBox, type YBoxProps } from '../layout/YBox';
+import { createStyles } from '@crossed/styled';
-export const Ul = styled(withDefaultProps(YBox, { role: 'list' }), (t) => ({
- marginTop: t.space.xl,
+const useList = createStyles((t) => ({
+ ul: { base: { marginTop: t.space.xl } },
+ li: { base: { gap: t.space.md, marginBottom: t.space.md } },
+ disc: { base: { fontSize: 9 } },
}));
-export const Li = styled(
- memo(
- forwardRef(({ children, ...props }: Omit, ref: any) => (
-
-
- {children as any}
-
- ))
- ),
- (t) => ({ gap: t.space.md, marginBottom: t.space.md })
-);
+export type UlProps = YBoxProps;
+export const Ul = (props: UlProps) => {
+ return ;
+};
-export const Disc = styled(
- memo(
- forwardRef((props: Omit, ref: any) => (
-
- {'\u2B24' + ' '}
-
- ))
- ),
- { fontSize: 9 }
-);
+export type LiProps = XBoxProps;
+export const Li = ({
+ children,
+ active,
+ hover,
+ focus,
+ style,
+ className,
+ ...props
+}: LiProps) => {
+ return (
+
+