[WEB-5459] feat(codemods): add function declaration transformer with tests (#8137)

- Add jscodeshift-based codemod to convert arrow function components to function declarations
- Support React.FC, observer-wrapped, and forwardRef components
- Include comprehensive test suite covering edge cases
- Add npm script to run transformer across codebase
- Target only .tsx files in source directories, excluding node_modules and declaration files

* [WEB-5459] chore: updates after running codemod

---------

Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>
This commit is contained in:
Aaron 2025-11-20 19:09:40 +07:00 committed by GitHub
parent 90866fb925
commit 83fdebf64d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
1771 changed files with 17003 additions and 13856 deletions

View file

@ -0,0 +1,5 @@
{
"printWidth": 80,
"tabWidth": 2,
"trailingComma": "es5"
}

View file

@ -0,0 +1,483 @@
import {
API,
FileInfo,
Options,
TSTypeReference,
JSCodeshift,
Identifier,
BlockStatement,
VariableDeclarator,
Expression,
Pattern,
SpreadElement,
JSXNamespacedName,
ASTNode,
Node,
FunctionDeclaration,
} from "jscodeshift";
const COMPONENT_TYPE_NAMES = new Set([
"FC",
"FunctionComponent",
"VFC",
"VoidFunctionComponent",
]);
const COMPONENT_NAME_PATTERN = /^[A-Z]/;
function isReactComponentType(typeReference: TSTypeReference, j: JSCodeshift) {
const typeName = typeReference.typeName;
if (!typeName) {
return false;
}
if (j.Identifier.check(typeName)) {
return COMPONENT_TYPE_NAMES.has(typeName.name);
}
if (
j.TSQualifiedName.check(typeName) &&
j.Identifier.check(typeName.left) &&
j.Identifier.check(typeName.right)
) {
return (
typeName.left.name === "React" &&
COMPONENT_TYPE_NAMES.has(typeName.right.name)
);
}
return false;
}
function isComponentNameIdentifier(identifier: Identifier | null | undefined) {
if (!identifier) {
return false;
}
return COMPONENT_NAME_PATTERN.test(identifier.name);
}
function addComments(target: Node, comments: NonNullable<Node["comments"]>) {
if (!comments || comments.length === 0) {
return;
}
target.comments ||= [];
target.comments.push(...comments);
}
function copyOuterComments(source: Node, target: Node, j: JSCodeshift) {
if (!j.Node.check(source) || !j.Node.check(target) || !source.comments) {
return;
}
const outerComments = source.comments.filter((c) => c.leading || c.trailing);
addComments(target, outerComments);
}
function ensureParamType(
param: Pattern,
propsType: ASTNode | null | undefined,
j: JSCodeshift
) {
if (!j.Pattern.check(param)) {
return;
}
if (!("typeAnnotation" in param)) {
return;
}
if (!propsType) {
return;
}
if (j.TSTypeReference.check(propsType) && propsType.typeName) {
param.typeAnnotation = j.tsTypeAnnotation(
propsType.typeParameters
? j.tsTypeReference(propsType.typeName, propsType.typeParameters)
: j.tsTypeReference(propsType.typeName)
);
return;
}
if (j.TSType.check(propsType)) {
// @ts-expect-error: jscodeshift types are too strict here
param.typeAnnotation = j.tsTypeAnnotation(propsType);
}
}
function toBlockBody(j: JSCodeshift, body: BlockStatement | Expression) {
if (j.BlockStatement.check(body)) {
return body;
}
// @ts-expect-error: jscodeshift types are too strict here
const returnStatement = j.returnStatement(body);
return j.blockStatement([returnStatement]);
}
function isFunction(node: Node, j: JSCodeshift) {
return (
j.ArrowFunctionExpression.check(node) || j.FunctionExpression.check(node)
);
}
function extractArrowFunction(
init: Expression | SpreadElement | JSXNamespacedName,
j: JSCodeshift
) {
if (isFunction(init, j)) {
return init;
}
// If it's a CallExpression like observer(() => {}), extract the arrow function
if (j.CallExpression.check(init)) {
const firstArg = init.arguments?.[0];
if (firstArg && isFunction(firstArg, j)) {
return firstArg;
}
}
return;
}
function extractPropsTypeFromWrapper(
init: Expression | SpreadElement | JSXNamespacedName,
j: JSCodeshift
) {
// If it's a CallExpression like observer<React.FC<Props>>((props) => {})
// Extract the Props type from React.FC<Props>
if (!j.CallExpression.check(init)) {
return;
}
if (!("typeParameters" in init)) {
return;
}
const typeParameters = init.typeParameters;
if (!j.TSTypeParameterInstantiation.check(typeParameters)) {
return;
}
const typeParam = typeParameters.params?.[0];
if (
!j.TSTypeReference.check(typeParam) ||
!isReactComponentType(typeParam, j)
) {
return;
}
// Extract the generic type from React.FC<PropsType>
return typeParam.typeParameters?.params?.[0];
}
function isReactForwardRef(
init: Expression | SpreadElement | JSXNamespacedName,
j: JSCodeshift
) {
if (!j.CallExpression.check(init)) {
return false;
}
const callee = init.callee;
// Check for React.forwardRef
if (
j.MemberExpression.check(callee) &&
j.Identifier.check(callee.object) &&
j.Identifier.check(callee.property)
) {
return (
callee.object.name === "React" && callee.property.name === "forwardRef"
);
}
// Check for forwardRef (imported directly)
if (j.Identifier.check(callee)) {
return callee.name === "forwardRef";
}
return false;
}
function extractForwardRefTypes(
init: Expression | SpreadElement | JSXNamespacedName,
j: JSCodeshift
) {
if (!isReactForwardRef(init, j)) {
return;
}
if (!("typeParameters" in init)) {
return;
}
const typeParameters = init.typeParameters;
// If no type parameters, we still want to apply default empty object for props
if (
!j.TSTypeParameterInstantiation.check(typeParameters) ||
typeParameters.params.length === 0
) {
return; // Let the default props type handling take care of it
}
const typeParams = typeParameters.params;
// React.forwardRef<ElementType, PropsType>
// If PropsType is not specified, use Record<string, unknown> to avoid ESLint errors
const [elementType] = typeParams;
if (!elementType) {
return;
}
const propsType =
typeParams.length >= 2 && typeParams[1]
? typeParams[1]
: j.tsTypeReference(
j.identifier("Record"),
j.tsTypeParameterInstantiation([
j.tsStringKeyword(),
j.tsUnknownKeyword(),
])
);
// Create React.ForwardedRef<ElementType> for the ref parameter
const refType = j.tsTypeReference(
j.tsQualifiedName(j.identifier("React"), j.identifier("ForwardedRef")),
j.tsTypeParameterInstantiation([elementType])
);
return { propsType, refType };
}
function isEmptyObjectType(type: ASTNode, j: JSCodeshift) {
return j.TSTypeLiteral.check(type) && type.members.length === 0;
}
function convertToFunction(
j: JSCodeshift,
declaration: VariableDeclarator,
init: Expression | SpreadElement | JSXNamespacedName,
propsType: ASTNode | null | undefined
) {
if (!j.Identifier.check(declaration.id)) {
throw new Error("Declaration id must be an identifier");
}
const componentName = declaration.id.name;
const arrowFn = extractArrowFunction(init, j);
if (!arrowFn) {
throw new Error("Expected ArrowFunctionExpression or FunctionExpression");
}
const params = arrowFn.params;
const body = toBlockBody(j, arrowFn.body);
const newFunction = j.functionDeclaration(
j.identifier(componentName),
params,
body
);
// Check if this is React.forwardRef and extract types for props and ref
const forwardRefTypes = extractForwardRefTypes(init, j);
if (forwardRefTypes) {
// Apply props type to first parameter
const [firstParam, secondParam] = newFunction.params;
if (j.Pattern.check(firstParam) && "typeAnnotation" in firstParam) {
ensureParamType(firstParam, forwardRefTypes.propsType, j);
}
// Apply ref type to second parameter
if (j.Pattern.check(secondParam) && "typeAnnotation" in secondParam) {
ensureParamType(secondParam, forwardRefTypes.refType, j);
}
} else if (newFunction.params.length > 0) {
const [firstParam] = newFunction.params;
if (firstParam) {
ensureParamType(firstParam, propsType, j);
}
} else if (propsType && !isEmptyObjectType(propsType, j)) {
// If there are no params but a non-empty propsType exists, add _props parameter
const propsParam = j.identifier("_props");
ensureParamType(propsParam, propsType, j);
newFunction.params.push(propsParam);
}
if (arrowFn.returnType) {
newFunction.returnType = arrowFn.returnType;
}
// Preserve type parameters (generics) from arrow function
if (arrowFn.typeParameters) {
newFunction.typeParameters = arrowFn.typeParameters;
}
newFunction.async = arrowFn.async;
newFunction.generator = arrowFn.generator;
return newFunction;
}
function containsJsx(j: JSCodeshift, body: ASTNode) {
return (
j(body).find(j.JSXElement).paths().length > 0 ||
j(body).find(j.JSXFragment).paths().length > 0
);
}
function toFunctionExpression(
j: JSCodeshift,
declaration: FunctionDeclaration
) {
const expression = j.functionExpression(
declaration.id,
declaration.params,
declaration.body,
declaration.generator,
declaration.async
);
expression.returnType = declaration.returnType;
expression.typeParameters = declaration.typeParameters;
return expression;
}
export default function transform(file: FileInfo, api: API, options: Options) {
const baseJ = api.jscodeshift;
const j =
typeof baseJ.withParser === "function" ? baseJ.withParser("tsx") : baseJ;
const root = j(file.source);
root
.find(j.VariableDeclaration)
.filter((path) => {
const [firstDeclaration] = path.node.declarations;
if (!j.VariableDeclarator.check(firstDeclaration)) {
return false;
}
if (
!j.Identifier.check(firstDeclaration.id) ||
!isComponentNameIdentifier(firstDeclaration.id)
) {
return false;
}
const init = firstDeclaration.init;
if (!init) {
return false;
}
const functionToCheck = extractArrowFunction(init, j);
if (!functionToCheck) {
return false;
}
if (file.path && !file.path.endsWith(".tsx")) {
if (!containsJsx(j, functionToCheck)) {
return false;
}
}
return true;
})
.forEach((path) => {
const [firstDeclaration] = path.node.declarations;
if (!j.VariableDeclarator.check(firstDeclaration)) {
return;
}
const init = firstDeclaration.init;
if (!init) {
return;
}
let typeAnnotation: ASTNode | null | undefined;
if (j.Identifier.check(firstDeclaration.id)) {
typeAnnotation = firstDeclaration.id.typeAnnotation?.typeAnnotation;
}
// Try to get props type from variable type annotation first
let propsType: ASTNode | undefined =
j.TSTypeReference.check(typeAnnotation) &&
isReactComponentType(typeAnnotation, j)
? typeAnnotation.typeParameters?.params?.[0]
: undefined;
// If no props type from variable annotation, try to extract from wrapper's type parameters
if (!propsType) {
propsType = extractPropsTypeFromWrapper(init, j);
}
const newFunction = convertToFunction(
j,
firstDeclaration,
init,
propsType
);
const originalNode = path.node;
// Check if init is wrapped in a call expression (e.g., observer(...))
const hasWrapper = j.CallExpression.check(init);
if (hasWrapper) {
// Preserve the wrapper by keeping it as a const assignment
// e.g., export const Foo = observer(() => {}) becomes export const Foo = observer(function Foo() {...})
// Convert function declaration to function expression for wrapping
const functionExpression = toFunctionExpression(j, newFunction);
const wrappedFunction = j.callExpression(init.callee, [
functionExpression,
]);
if (!j.Identifier.check(firstDeclaration.id)) {
return;
}
const newDeclarator = j.variableDeclarator(
j.identifier(firstDeclaration.id.name),
wrappedFunction
);
const newVarDecl = j.variableDeclaration("const", [newDeclarator]);
// Copy comments from original declaration to new function
copyOuterComments(originalNode, newVarDecl, j);
j(path).replaceWith(newVarDecl);
return;
}
// Copy outer comments from original declaration to new function
copyOuterComments(originalNode, newFunction, j);
// Copy comments from VariableDeclarator (e.g. export /* comment */ const Foo)
if (firstDeclaration.comments) {
addComments(newFunction, firstDeclaration.comments);
}
// Copy comments from arrow function
if (init.comments) {
addComments(newFunction, init.comments);
}
j(path).replaceWith(newFunction);
});
const quote = options.quote ?? '"';
const source = root.toSource({
quote,
});
return source;
}

View file

@ -0,0 +1,366 @@
# jscodeshift Instructions
## API Reference
### Core API
#### `jscodeshift`
The main function that returns the jscodeshift instance.
- **Parameters**: `source` (String)
- **Example**:
```javascript
const j = jscodeshift(sourceCode);
```
### Node Traversal APIs
#### `find`
Finds nodes that match the provided type.
- **Parameters**: `type` (String or Function)
- **Example**:
```javascript
const variableDeclarations = j.find(j.VariableDeclaration);
```
#### `findImportDeclarations`
Finds all ImportDeclarations optionally filtered by name.
- **Parameters**: `sourcePath` (String)
- **Example**:
```javascript
const routerImports = j.findImportDeclarations("react-router-dom");
```
#### `closestScope`
Finds the closest enclosing scope of a node.
- **Example**:
```javascript
const closestScopes = j.find(j.Identifier).closestScope();
```
#### `closest`
Finds the nearest parent node that matches the specified type.
- **Parameters**: `type` (String or Function)
- **Example**:
```javascript
const closestFunction = j.find(j.Identifier).closest(j.FunctionDeclaration);
```
#### `getVariableDeclarators`
Retrieves variable declarators from the current collection.
- **Parameters**: `callback` (Function)
- **Example**:
```javascript
const variableDeclarators = j.find(j.Identifier).getVariableDeclarators((path) => path.value.name);
```
#### `findVariableDeclarators`
Finds variable declarators by name.
- **Parameters**: `name` (String)
- **Example**:
```javascript
const variableDeclarators = j.findVariableDeclarators("a");
```
#### `filter`
Filters nodes based on a predicate function.
- **Parameters**: `predicate` (Function)
- **Example**:
```javascript
const constDeclarations = j.find(j.VariableDeclaration).filter((path) => path.node.kind === "const");
```
#### `forEach`
Iterates over each node in the collection.
- **Parameters**: `callback` (Function)
- **Example**:
```javascript
j.find(j.VariableDeclaration).forEach((path) => {
console.log(path.node);
});
```
#### `some`
Checks if at least one element in the collection passes the test.
- **Parameters**: `callback` (Function)
- **Example**:
```javascript
const hasVariableA = root.find(j.VariableDeclarator).some((path) => path.node.id.name === "a");
```
#### `every`
Checks if all elements in the collection pass the test.
- **Parameters**: `callback` (Function)
- **Example**:
```javascript
const allAreConst = root.find(j.VariableDeclaration).every((path) => path.node.kind === "const");
```
#### `map`
Maps each node in the collection to a new value.
- **Parameters**: `callback` (Function)
- **Example**:
```javascript
const variableNames = j.find(j.VariableDeclaration).map((path) => path.node.declarations.map((decl) => decl.id.name));
```
#### `size`
Returns the number of nodes in the collection.
- **Example**:
```javascript
const numberOfNodes = j.find(j.VariableDeclaration).size();
```
#### `length`
Returns the number of elements in the collection.
- **Example**:
```javascript
const varCount = root.find(j.VariableDeclarator).length;
```
#### `nodes`
Returns the AST nodes in the collection.
- **Example**:
```javascript
const nodes = j.find(j.VariableDeclaration).nodes();
```
#### `paths`
Returns the paths of the found nodes.
- **Example**:
```javascript
const paths = j.find(j.VariableDeclaration).paths();
```
#### `getAST`
Returns the root AST node of the collection.
- **Example**:
```javascript
const ast = root.getAST();
```
#### `get`
Gets the first node in the collection.
- **Example**:
```javascript
const firstVariableDeclaration = j.find(j.VariableDeclaration).get();
```
#### `at`
Navigates to a specific path in the AST.
- **Parameters**: `index` (Number)
- **Example**:
```javascript
const secondVariableDeclaration = j.find(j.VariableDeclaration).at(1);
```
#### `getTypes`
Returns the set of node types present in the collection.
- **Example**:
```javascript
const types = root.find(j.VariableDeclarator).getTypes();
```
#### `isOfType`
Checks if the node in the collection is of a specific type.
- **Parameters**: `type` (String)
- **Example**:
```javascript
const isVariableDeclarator = root.find(j.VariableDeclarator).at(0).isOfType("VariableDeclarator");
```
### Node Transformation APIs
#### `replaceWith`
Replaces the current node(s) with a new node.
- **Parameters**: `newNode` (Node or Function)
- **Example**:
```javascript
j.find(j.Identifier).replaceWith((path) => j.identifier(path.node.name.toUpperCase()));
```
#### `insertBefore`
Inserts a node before the current node.
- **Parameters**: `newNode` (Node)
- **Example**:
```javascript
j.find(j.FunctionDeclaration).insertBefore(j.expressionStatement(j.stringLiteral("Inserted before")));
```
#### `insertAfter`
Inserts a node after the current node.
- **Parameters**: `newNode` (Node)
- **Example**:
```javascript
j.find(j.FunctionDeclaration).insertAfter(j.expressionStatement(j.stringLiteral("Inserted after")));
```
#### `remove`
Removes the current node(s).
- **Example**:
```javascript
j.find(j.VariableDeclaration).remove();
```
#### `renameTo`
Renames the nodes in the collection to a new name.
- **Parameters**: `newName` (String)
- **Example**:
```javascript
root.find(j.Identifier, { name: "a" }).renameTo("x");
```
#### `toSource`
Converts the transformed AST back to source code.
- **Parameters**: `options` (Object)
- **Example**:
```javascript
const transformedSource = j.toSource({ quote: "single" });
```
## AST Grammar
jscodeshift provides 278 node types which are mapped to their corresponding node type in `ast-types`.
### Common Node Types
- **AnyTypeAnnotation**: A type annotation representing any type.
- **ArrayExpression**: Represents an array literal.
- **ArrayPattern**: A pattern that matches an array from a destructuring assignment.
- **ArrayTypeAnnotation**: A type annotation for arrays.
- **ArrowFunctionExpression**: An arrow function expression.
- **AssignmentExpression**: Represents an assignment expression.
- **AssignmentPattern**: A pattern that matches an assignment from a destructuring assignment.
- **AwaitExpression**: Represents an await expression.
- **BigIntLiteral**: A literal representing a big integer.
- **BinaryExpression**: Represents a binary expression.
- **BlockStatement**: Represents a block statement.
- **BooleanLiteral**: A literal representing a boolean value.
- **BreakStatement**: Represents a break statement.
- **CallExpression**: Represents a call expression.
- **CatchClause**: Represents a catch clause in a try statement.
- **ClassDeclaration**: Represents a class declaration.
- **ClassExpression**: Represents a class expression.
- **ClassMethod**: Represents a method of a class.
- **ClassProperty**: Represents a property of a class.
- **Comment**: Represents a comment in the code.
- **ConditionalExpression**: Represents a conditional expression (ternary).
- **ContinueStatement**: Represents a continue statement.
- **DebuggerStatement**: Represents a debugger statement.
- **Declaration**: Represents a declaration in the code.
- **DoWhileStatement**: Represents a do…while statement.
- **ExportAllDeclaration**: Represents an export all declaration.
- **ExportDeclaration**: Represents an export declaration.
- **ExportDefaultDeclaration**: Represents an export default declaration.
- **ExportNamedDeclaration**: Represents a named export declaration.
- **ExpressionStatement**: Represents an expression statement.
- **File**: Represents a file in the AST.
- **ForInStatement**: Represents a for-in statement.
- **ForOfStatement**: Represents a for-of statement.
- **ForStatement**: Represents a for statement.
- **FunctionDeclaration**: Represents a function declaration.
- **FunctionExpression**: Represents a function expression.
- **Identifier**: Represents an identifier.
- **IfStatement**: Represents an if statement.
- **ImportDeclaration**: Represents an import declaration.
- **ImportDefaultSpecifier**: Represents a default import specifier.
- **ImportNamespaceSpecifier**: Represents a namespace import specifier.
- **ImportSpecifier**: Represents an import specifier.
- **InterfaceDeclaration**: Represents an interface declaration.
- **JSXAttribute**: Represents an attribute in a JSX element.
- **JSXElement**: Represents a JSX element.
- **JSXExpressionContainer**: Represents an expression container in JSX.
- **JSXFragment**: Represents a JSX fragment.
- **JSXIdentifier**: Represents an identifier in JSX.
- **JSXText**: Represents text in JSX.
- **Literal**: Represents a literal value.
- **LogicalExpression**: Represents a logical expression.
- **MemberExpression**: Represents a member expression.
- **MethodDefinition**: Represents a method definition.
- **NewExpression**: Represents a new expression.
- **ObjectExpression**: Represents an object expression.
- **ObjectPattern**: Represents an object pattern for destructuring.
- **ObjectProperty**: Represents a property in an object.
- **Program**: Represents the entire program.
- **Property**: Represents a property in an object.
- **ReturnStatement**: Represents a return statement.
- **SpreadElement**: Represents a spread element in an array or function call.
- **StringLiteral**: Represents a string literal.
- **SwitchCase**: Represents a case in a switch statement.
- **SwitchStatement**: Represents a switch statement.
- **TemplateLiteral**: Represents a template literal.
- **ThisExpression**: Represents the `this` expression.
- **ThrowStatement**: Represents a throw statement.
- **TryStatement**: Represents a try statement.
- **TSAnyKeyword**: Represents the TypeScript `any` keyword.
- **TSArrayType**: Represents a TypeScript array type.
- **TSAsExpression**: Represents a TypeScript as-expression.
- **TSBooleanKeyword**: Represents the TypeScript `boolean` keyword.
- **TSDeclareFunction**: Represents a TypeScript function declaration.
- **TSEnumDeclaration**: Represents a TypeScript enum declaration.
- **TSInterfaceDeclaration**: Represents a TypeScript interface declaration.
- **TSNumberKeyword**: Represents the TypeScript `number` keyword.
- **TSStringKeyword**: Represents the TypeScript `string` keyword.
- **TSTypeAliasDeclaration**: Represents a TypeScript type alias declaration.
- **TSTypeAnnotation**: Represents a TypeScript type annotation.
- **TSTypeReference**: Represents a type reference in TypeScript.
- **TSUnionType**: Represents a union type in TypeScript.
- **UnaryExpression**: Represents a unary expression.
- **VariableDeclaration**: Represents a variable declaration.
- **VariableDeclarator**: Represents a variable declarator.
- **WhileStatement**: Represents a while statement.
For a complete list and detailed structure of each node, refer to the [AST Grammar documentation](https://jscodeshift.com/build/ast-grammar/).

View file

@ -0,0 +1,15 @@
{
"name": "@plane/codemods",
"version": "1.1.0",
"private": true,
"scripts": {
"test": "vitest run",
"function-declaration": "jscodeshift -t ./function-declaration.ts --extensions=tsx --parser=tsx ../../apps/*/app ../../apps/*/ce ../../apps/*/core ../../apps/*/ee ../../apps/*/helpers ../../packages/*/src --ignore-pattern='**/node_modules/**' --ignore-pattern='**/dist/**' --ignore-pattern='**/build/**' --ignore-pattern='**/*.d.ts'"
},
"devDependencies": {
"@hypermod/utils": "^0.7.1",
"@types/jscodeshift": "^17.3.0",
"jscodeshift": "^17.3.0",
"vitest": "^4.0.8"
}
}

View file

@ -0,0 +1,465 @@
import { describe, it, expect } from "vitest";
import { applyTransform } from "@hypermod/utils";
import * as transformer from "../function-declaration";
describe("function-declaration", () => {
it("should convert arrow function components to function declarations", async () => {
const result = await applyTransform(
transformer,
`
import React from "react";
export const MyComponent: React.FC<{}> = () => {
return <div>Hello, world!</div>;
};
`,
{ parser: "tsx" },
);
expect(result).toMatchInlineSnapshot(`
"import React from "react";
export function MyComponent() {
return <div>Hello, world!</div>;
}"
`);
});
it("should handle components with props", async () => {
const result = await applyTransform(
transformer,
`
import React from "react";
interface IMyComponentProps {
name: string;
}
export const MyComponent: React.FC<IMyComponentProps> = ({ name }) => {
return <div>Hello, {name}!</div>;
};
`,
{ parser: "tsx" },
);
expect(result).toMatchInlineSnapshot(`
"import React from "react";
interface IMyComponentProps {
name: string;
}
export function MyComponent(
{
name
}: IMyComponentProps
) {
return <div>Hello, {name}!</div>;
}"
`);
});
it("should preserve default props", async () => {
const result = await applyTransform(
transformer,
`
import React from "react";
interface IMyComponentProps {
name?: string;
}
export const MyComponent: React.FC<IMyComponentProps> = ({ name = "world" }) => {
return <div>Hello, {name}!</div>;
};
`,
{ parser: "tsx" },
);
expect(result).toMatchInlineSnapshot(`
"import React from "react";
interface IMyComponentProps {
name?: string;
}
export function MyComponent(
{
name = "world"
}: IMyComponentProps
) {
return <div>Hello, {name}!</div>;
}"
`);
});
it("should not transform non-component arrow functions", async () => {
const result = await applyTransform(
transformer,
`
const myFunction = () => {
return "hello";
};
`,
{ parser: "tsx" },
);
expect(result).toMatchInlineSnapshot(`
"const myFunction = () => {
return "hello";
};"
`);
});
it("should handle observer-wrapped components", async () => {
const result = await applyTransform(
transformer,
`
import { observer } from "mobx-react";
export const WorkspaceAnalyticsHeader = observer(() => {
return <div>Analytics</div>;
});
`,
{ parser: "tsx" },
);
expect(result).toMatchInlineSnapshot(`
"import { observer } from "mobx-react";
export const WorkspaceAnalyticsHeader = observer(function WorkspaceAnalyticsHeader() {
return <div>Analytics</div>;
});"
`);
});
it("should handle inline arrow function components", async () => {
const result = await applyTransform(
transformer,
`
export const StarUsOnGitHubLink = () => {
return <a href="https://github.com">Star us</a>;
};
`,
{ parser: "tsx" },
);
expect(result).toMatchInlineSnapshot(`
"export function StarUsOnGitHubLink() {
return <a href="https://github.com">Star us</a>;
}"
`);
});
it("should handle React.FC type without generics", async () => {
const result = await applyTransform(
transformer,
`
import type { FC } from "react";
export const ProjectAppSidebar: FC = observer(() => {
return <div>Sidebar</div>;
});
`,
{ parser: "tsx" },
);
expect(result).toMatchInlineSnapshot(`
"import type { FC } from "react";
export const ProjectAppSidebar = observer(function ProjectAppSidebar() {
return <div>Sidebar</div>;
});"
`);
});
it("should handle inline JSX arrow function", async () => {
const result = await applyTransform(
transformer,
`
export const DateAlert = (props: TDateAlertProps) => <></>;
`,
{ parser: "tsx" },
);
expect(result).toMatchInlineSnapshot(`
"export function DateAlert(props: TDateAlertProps) {
return <></>;
}"
`);
});
it("should handle observer with generic type parameters", async () => {
const result = await applyTransform(
transformer,
`
import { observer } from "mobx-react";
export const InstanceProvider = observer<React.FC<React.PropsWithChildren>>((props) => {
const { children } = props;
return <>{children}</>;
});
`,
{ parser: "tsx" },
);
expect(result).toMatchInlineSnapshot(`
"import { observer } from "mobx-react";
export const InstanceProvider = observer(function InstanceProvider(props: React.PropsWithChildren) {
const { children } = props;
return <>{children}</>;
});"
`);
});
it("should not add double semicolons after use client directive", async () => {
const result = await applyTransform(
transformer,
`
"use client";
import { observer } from "mobx-react";
export const MyComponent = observer(() => {
return <div>Hello</div>;
});
`,
{ parser: "tsx" },
);
expect(result).toMatchInlineSnapshot(`
""use client";
import { observer } from "mobx-react";
export const MyComponent = observer(function MyComponent() {
return <div>Hello</div>;
});"
`);
});
it("should preserve generic type parameters in wrapper functions", async () => {
const result = await applyTransform(
transformer,
`
import React from "react";
export const ScatterChart = React.memo(<K extends string, T extends string>(props: TScatterChartProps<K, T>) => {
return <div>Chart</div>;
});
`,
{ parser: "tsx" },
);
expect(result).toMatchInlineSnapshot(`
"import React from "react";
export const ScatterChart = React.memo(
function ScatterChart<K extends string, T extends string>(props: TScatterChartProps<K, T>) {
return <div>Chart</div>;
}
);"
`);
});
it("should preserve generic type parameters on React.forwardRef", async () => {
const result = await applyTransform(
transformer,
`
import React from "react";
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
return <button ref={ref}>Click me</button>;
});
`,
{ parser: "tsx" },
);
expect(result).toMatchInlineSnapshot(`
"import React from "react";
const Button = React.forwardRef(
function Button(props: ButtonProps, ref: React.ForwardedRef<HTMLButtonElement>) {
return <button ref={ref}>Click me</button>;
}
);"
`);
});
it("should prefix unused props parameter with underscore", async () => {
const result = await applyTransform(
transformer,
`
import type { TCallbackMentionComponentProps } from "@plane/editor";
export const EditorAdditionalMentionsRoot: React.FC<TCallbackMentionComponentProps> = () => null;
`,
{ parser: "tsx" },
);
expect(result).toMatchInlineSnapshot(`
"import type { TCallbackMentionComponentProps } from "@plane/editor";
export function EditorAdditionalMentionsRoot(_props: TCallbackMentionComponentProps) {
return null;
}"
`);
});
it("should add Record<string, unknown> type for React.forwardRef with only element type", async () => {
const result = await applyTransform(
transformer,
`
import { forwardRef } from "react";
const ListLoaderItemRow = forwardRef<HTMLDivElement>((props, ref) => (
<div ref={ref}>Content</div>
));
`,
{ parser: "tsx" },
);
expect(result).toMatchInlineSnapshot(`
"import { forwardRef } from "react";
const ListLoaderItemRow = forwardRef(
function ListLoaderItemRow(props: Record<string, unknown>, ref: React.ForwardedRef<HTMLDivElement>) {
return (<div ref={ref}>Content</div>);
}
);"
`);
});
it("should preserve comments in function body", async () => {
const result = await applyTransform(
transformer,
`
export const PreloadResources = () => (
// usePreloadResources();
null
);
`,
{ parser: "tsx" },
);
expect(result).toMatchInlineSnapshot(`
"export function PreloadResources() {
return (
// usePreloadResources();
(null)
);
}"
`);
});
it("should preserve leading comments before export declaration", async () => {
const result = await applyTransform(
transformer,
`
"use client";
// TODO: Check if we need this
// https://nextjs.org/docs/app/api-reference/functions/generate-metadata#link-relpreload
// export const usePreloadResources = () => {
// useEffect(() => {
// const preloadItem = (url: string) => {
// ReactDOM.preload(url, { as: "fetch", crossOrigin: "use-credentials" });
// };
//
// const urls = [
// \`\${process.env.VITE_API_BASE_URL}/api/instances/\`,
// \`\${process.env.VITE_API_BASE_URL}/api/users/me/\`,
// \`\${process.env.VITE_API_BASE_URL}/api/users/me/profile/\`,
// \`\${process.env.VITE_API_BASE_URL}/api/users/me/settings/\`,
// \`\${process.env.VITE_API_BASE_URL}/api/users/me/workspaces/?v=\${Date.now()}\`,
// ];
//
// urls.forEach((url) => preloadItem(url));
// }, []);
// };
export const PreloadResources = () =>
// usePreloadResources();
null;
`,
{ parser: "tsx" },
);
expect(result).toMatchInlineSnapshot(`
""use client";
// TODO: Check if we need this
// https://nextjs.org/docs/app/api-reference/functions/generate-metadata#link-relpreload
// export const usePreloadResources = () => {
// useEffect(() => {
// const preloadItem = (url: string) => {
// ReactDOM.preload(url, { as: "fetch", crossOrigin: "use-credentials" });
// };
//
// const urls = [
// \`\${process.env.VITE_API_BASE_URL}/api/instances/\`,
// \`\${process.env.VITE_API_BASE_URL}/api/users/me/\`,
// \`\${process.env.VITE_API_BASE_URL}/api/users/me/profile/\`,
// \`\${process.env.VITE_API_BASE_URL}/api/users/me/settings/\`,
// \`\${process.env.VITE_API_BASE_URL}/api/users/me/workspaces/?v=\${Date.now()}\`,
// ];
//
// urls.forEach((url) => preloadItem(url));
// }, []);
// };
export function PreloadResources() {
return (
// usePreloadResources();
null
);
}"
`);
});
it("should preserve leading comments before wrapped export declaration", async () => {
const result = await applyTransform(
transformer,
`
// This is a wrapped component
// It uses observer for reactivity
export const MyObserverComponent = observer(() => {
return <div>Observer component</div>;
});
`,
{ parser: "tsx" },
);
expect(result).toMatchInlineSnapshot(`
"// This is a wrapped component
// It uses observer for reactivity
export const MyObserverComponent = observer(function MyObserverComponent() {
return <div>Observer component</div>;
});"
`);
});
it("should preserve trailing comments on exported variable declaration", async () => {
const result = await applyTransform(
transformer,
`
export const Foo = () => <div />; // trailing comment
`,
{ parser: "tsx" },
);
expect(result).toContain("// trailing comment");
});
it("should preserve leading comments on exported variable declaration inside export", async () => {
const result = await applyTransform(
transformer,
`
export /* leading comment */ const Foo = () => <div />;
`,
{ parser: "tsx" },
);
expect(result).toContain("/* leading comment */");
});
});

View file

@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}
}

View file

@ -0,0 +1,7 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "node",
},
});

View file

@ -1,14 +1,16 @@
import type { Editor } from "@tiptap/core";
import { LinkViewContainer } from "@/components/editors/link-view-container";
export const LinkContainer = ({
export function LinkContainer({
editor,
containerRef,
}: {
editor: Editor;
containerRef: React.RefObject<HTMLDivElement>;
}) => (
<>
<LinkViewContainer editor={editor} containerRef={containerRef} />
</>
);
}) {
return (
<>
<LinkViewContainer editor={editor} containerRef={containerRef} />
</>
);
}

View file

@ -26,7 +26,7 @@ const extensionRegistry: TDocumentEditorAdditionalExtensionsRegistry[] = [
},
];
export const DocumentEditorAdditionalExtensions = (props: TDocumentEditorAdditionalExtensionsProps) => {
export function DocumentEditorAdditionalExtensions(props: TDocumentEditorAdditionalExtensionsProps) {
const { disabledExtensions, flaggedExtensions } = props;
const documentExtensions = extensionRegistry
@ -34,4 +34,4 @@ export const DocumentEditorAdditionalExtensions = (props: TDocumentEditorAdditio
.map((config) => config.getExtension(props));
return documentExtensions;
};
}

View file

@ -30,7 +30,7 @@ const extensionRegistry: TRichTextEditorAdditionalExtensionsRegistry[] = [
},
];
export const RichTextEditorAdditionalExtensions = (props: TRichTextEditorAdditionalExtensionsProps) => {
export function RichTextEditorAdditionalExtensions(props: TRichTextEditorAdditionalExtensionsProps) {
const { disabledExtensions, flaggedExtensions } = props;
const extensions: Extensions = extensionRegistry
@ -39,4 +39,4 @@ export const RichTextEditorAdditionalExtensions = (props: TRichTextEditorAdditio
.filter((extension): extension is AnyExtension => extension !== undefined);
return extensions;
};
}

View file

@ -14,7 +14,7 @@ import { DocumentEditorSideEffects } from "@/plane-editor/components/document-ed
// types
import type { EditorRefApi, ICollaborativeDocumentEditorProps } from "@/types";
const CollaborativeDocumentEditor: React.FC<ICollaborativeDocumentEditorProps> = (props) => {
function CollaborativeDocumentEditor(props: ICollaborativeDocumentEditorProps) {
const {
aiHandler,
bubbleMenuEnabled = true,
@ -105,13 +105,14 @@ const CollaborativeDocumentEditor: React.FC<ICollaborativeDocumentEditorProps> =
/>
</>
);
};
}
const CollaborativeDocumentEditorWithRef = React.forwardRef<EditorRefApi, ICollaborativeDocumentEditorProps>(
(props, ref) => (
<CollaborativeDocumentEditor {...props} forwardedRef={ref as React.MutableRefObject<EditorRefApi | null>} />
)
);
const CollaborativeDocumentEditorWithRef = React.forwardRef(function CollaborativeDocumentEditorWithRef(
props: ICollaborativeDocumentEditorProps,
ref: React.ForwardedRef<EditorRefApi>
) {
return <CollaborativeDocumentEditor {...props} forwardedRef={ref as React.MutableRefObject<EditorRefApi | null>} />;
});
CollaborativeDocumentEditorWithRef.displayName = "CollaborativeDocumentEditorWithRef";

View file

@ -18,7 +18,7 @@ import { DocumentEditorAdditionalExtensions } from "@/plane-editor/extensions";
// types
import type { EditorRefApi, IDocumentEditorProps } from "@/types";
const DocumentEditor = (props: IDocumentEditorProps) => {
function DocumentEditor(props: IDocumentEditorProps) {
const {
bubbleMenuEnabled = false,
containerClassName,
@ -99,11 +99,14 @@ const DocumentEditor = (props: IDocumentEditorProps) => {
isTouchDevice={!!isTouchDevice}
/>
);
};
}
const DocumentEditorWithRef = forwardRef<EditorRefApi, IDocumentEditorProps>((props, ref) => (
<DocumentEditor {...props} forwardedRef={ref as MutableRefObject<EditorRefApi | null>} />
));
const DocumentEditorWithRef = forwardRef(function DocumentEditorWithRef(
props: IDocumentEditorProps,
ref: React.ForwardedRef<EditorRefApi>
) {
return <DocumentEditor {...props} forwardedRef={ref as MutableRefObject<EditorRefApi | null>} />;
});
DocumentEditorWithRef.displayName = "DocumentEditorWithRef";

View file

@ -6,7 +6,7 @@ type Props = {
className?: string;
};
export const DocumentContentLoader = (props: Props) => {
export function DocumentContentLoader(props: Props) {
const { className } = props;
return (
@ -48,4 +48,4 @@ export const DocumentContentLoader = (props: Props) => {
</Loader>
</div>
);
};
}

View file

@ -23,7 +23,7 @@ type Props = {
disabledExtensions: IEditorProps["disabledExtensions"];
};
export const PageRenderer = (props: Props) => {
export function PageRenderer(props: Props) {
const {
aiHandler,
bubbleMenuEnabled,
@ -71,4 +71,4 @@ export const PageRenderer = (props: Props) => {
)}
</div>
);
};
}

View file

@ -20,7 +20,7 @@ type Props = {
isTouchDevice: boolean;
};
export const EditorContainer: FC<Props> = (props) => {
export function EditorContainer(props: Props) {
const { children, displayConfig, editor, editorContainerClassName, id, isTouchDevice } = props;
// refs
const containerRef = useRef<HTMLDivElement>(null);
@ -100,4 +100,4 @@ export const EditorContainer: FC<Props> = (props) => {
</div>
</>
);
};
}

View file

@ -9,7 +9,7 @@ type Props = {
tabIndex?: number;
};
export const EditorContentWrapper: FC<Props> = (props) => {
export function EditorContentWrapper(props: Props) {
const { editor, children, tabIndex, id } = props;
return (
@ -18,4 +18,4 @@ export const EditorContentWrapper: FC<Props> = (props) => {
{children}
</div>
);
};
}

View file

@ -16,7 +16,7 @@ type Props = IEditorProps & {
extensions: Extensions;
};
export const EditorWrapper: React.FC<Props> = (props) => {
export function EditorWrapper(props: Props) {
const {
children,
containerClassName,
@ -93,4 +93,4 @@ export const EditorWrapper: React.FC<Props> = (props) => {
</div>
</EditorContainer>
);
};
}

View file

@ -13,7 +13,7 @@ type Props = {
containerRef: React.RefObject<HTMLDivElement>;
};
export const LinkViewContainer: FC<Props> = ({ editor, containerRef }) => {
export function LinkViewContainer({ editor, containerRef }: Props) {
const [linkViewProps, setLinkViewProps] = useState<LinkViewProps>();
const [isOpen, setIsOpen] = useState(false);
const [virtualElement, setVirtualElement] = useState<Element | null>(null);
@ -201,4 +201,4 @@ export const LinkViewContainer: FC<Props> = ({ editor, containerRef }) => {
)}
</>
);
};
}

View file

@ -6,7 +6,7 @@ import { EnterKeyExtension } from "@/extensions";
// types
import type { EditorRefApi, ILiteTextEditorProps } from "@/types";
const LiteTextEditor: React.FC<ILiteTextEditorProps> = (props) => {
function LiteTextEditor(props: ILiteTextEditorProps) {
const { onEnterKeyPress, disabledExtensions, extensions: externalExtensions = [] } = props;
const extensions = useMemo(() => {
@ -20,11 +20,14 @@ const LiteTextEditor: React.FC<ILiteTextEditorProps> = (props) => {
}, [externalExtensions, disabledExtensions, onEnterKeyPress]);
return <EditorWrapper {...props} extensions={extensions} />;
};
}
const LiteTextEditorWithRef = forwardRef<EditorRefApi, ILiteTextEditorProps>((props, ref) => (
<LiteTextEditor {...props} forwardedRef={ref as React.MutableRefObject<EditorRefApi | null>} />
));
const LiteTextEditorWithRef = forwardRef(function LiteTextEditorWithRef(
props: ILiteTextEditorProps,
ref: React.ForwardedRef<EditorRefApi>
) {
return <LiteTextEditor {...props} forwardedRef={ref as React.MutableRefObject<EditorRefApi | null>} />;
});
LiteTextEditorWithRef.displayName = "LiteTextEditorWithRef";

View file

@ -9,7 +9,7 @@ import { RichTextEditorAdditionalExtensions } from "@/plane-editor/extensions/ri
// types
import type { EditorRefApi, IRichTextEditorProps } from "@/types";
const RichTextEditor: React.FC<IRichTextEditorProps> = (props) => {
function RichTextEditor(props: IRichTextEditorProps) {
const {
bubbleMenuEnabled = true,
disabledExtensions,
@ -54,11 +54,14 @@ const RichTextEditor: React.FC<IRichTextEditorProps> = (props) => {
)}
</EditorWrapper>
);
};
}
const RichTextEditorWithRef = forwardRef<EditorRefApi, IRichTextEditorProps>((props, ref) => (
<RichTextEditor {...props} forwardedRef={ref as React.MutableRefObject<EditorRefApi | null>} />
));
const RichTextEditorWithRef = forwardRef(function RichTextEditorWithRef(
props: IRichTextEditorProps,
ref: React.ForwardedRef<EditorRefApi>
) {
return <RichTextEditor {...props} forwardedRef={ref as React.MutableRefObject<EditorRefApi | null>} />;
});
RichTextEditorWithRef.displayName = "RichTextEditorWithRef";

View file

@ -14,26 +14,28 @@ type InputViewProps = {
autoFocus?: boolean;
};
const InputView = ({ label, value, placeholder, onChange, autoFocus }: InputViewProps) => (
<div className="flex flex-col gap-1">
<label className="inline-block font-semibold text-xs text-custom-text-400">{label}</label>
<input
placeholder={placeholder}
onClick={(e) => e.stopPropagation()}
className="w-[280px] outline-none bg-custom-background-90 text-custom-text-900 text-sm border border-custom-border-300 rounded-md p-2"
value={value}
onChange={(e) => onChange(e.target.value)}
autoFocus={autoFocus}
/>
</div>
);
function InputView({ label, value, placeholder, onChange, autoFocus }: InputViewProps) {
return (
<div className="flex flex-col gap-1">
<label className="inline-block font-semibold text-xs text-custom-text-400">{label}</label>
<input
placeholder={placeholder}
onClick={(e) => e.stopPropagation()}
className="w-[280px] outline-none bg-custom-background-90 text-custom-text-900 text-sm border border-custom-border-300 rounded-md p-2"
value={value}
onChange={(e) => onChange(e.target.value)}
autoFocus={autoFocus}
/>
</div>
);
}
type LinkEditViewProps = {
viewProps: LinkViewProps;
switchView: (view: LinkViews) => void;
};
export const LinkEditView = ({ viewProps }: LinkEditViewProps) => {
export function LinkEditView({ viewProps }: LinkEditViewProps) {
const { editor, from, to, url: initialUrl, text: initialText, closeLinkView } = viewProps;
// State
@ -147,4 +149,4 @@ export const LinkEditView = ({ viewProps }: LinkEditViewProps) => {
</div>
</div>
);
};
}

View file

@ -2,13 +2,13 @@ import { Copy, GlobeIcon, Link2Off, PencilIcon } from "lucide-react";
// components
import type { LinkViewProps, LinkViews } from "@/components/links";
export const LinkPreview = ({
export function LinkPreview({
viewProps,
switchView,
}: {
viewProps: LinkViewProps;
switchView: (view: LinkViews) => void;
}) => {
}) {
const { editor, from, to, url } = viewProps;
const removeLink = () => {
@ -52,4 +52,4 @@ export const LinkPreview = ({
</div>
</div>
);
};
}

View file

@ -16,7 +16,7 @@ export type LinkViewProps = {
closeLinkView: () => void;
};
export const LinkView = (props: LinkViewProps & { style: CSSProperties }) => {
export function LinkView(props: LinkViewProps & { style: CSSProperties }) {
const [currentView, setCurrentView] = useState<LinkViews>(props.view ?? "LinkPreview");
const [prevFrom, setPrevFrom] = useState(props.from);
@ -37,4 +37,4 @@ export const LinkView = (props: LinkViewProps & { style: CSSProperties }) => {
{currentView === "LinkEditView" && <LinkEditView viewProps={props} switchView={switchView} />}
</>
);
};
}

View file

@ -10,7 +10,7 @@ type Props = {
menu: TAIHandler["menu"];
};
export const AIFeaturesMenu: React.FC<Props> = (props) => {
export function AIFeaturesMenu(props: Props) {
const { menu } = props;
// states
const [isPopupVisible, setIsPopupVisible] = useState(false);
@ -94,4 +94,4 @@ export const AIFeaturesMenu: React.FC<Props> = (props) => {
</div>
</div>
);
};
}

View file

@ -34,7 +34,7 @@ export type BlockMenuOption = {
isDisabled?: boolean;
};
export const BlockMenu = (props: Props) => {
export function BlockMenu(props: Props) {
const { editor, workItemIdentifier } = props;
const [isOpen, setIsOpen] = useState(false);
const [isAnimatedIn, setIsAnimatedIn] = useState(false);
@ -245,4 +245,4 @@ export const BlockMenu = (props: Props) => {
</div>
</FloatingPortal>
);
};
}

View file

@ -14,7 +14,7 @@ type Props = {
editorState: EditorStateType;
};
export const TextAlignmentSelector: React.FC<Props> = (props) => {
export function TextAlignmentSelector(props: Props) {
const { editor, editorState } = props;
const menuItem = TextAlignItem(editor);
@ -80,4 +80,4 @@ export const TextAlignmentSelector: React.FC<Props> = (props) => {
))}
</div>
);
};
}

View file

@ -17,7 +17,7 @@ type Props = {
editorState: EditorStateType;
};
export const BubbleMenuColorSelector: FC<Props> = (props) => {
export function BubbleMenuColorSelector(props: Props) {
const { editor, editorState } = props;
// floating ui
const { options, getReferenceProps, getFloatingProps } = useFloatingMenu({});
@ -111,4 +111,4 @@ export const BubbleMenuColorSelector: FC<Props> = (props) => {
</section>
</FloatingMenuRoot>
);
};
}

View file

@ -16,7 +16,7 @@ type Props = {
editor: Editor;
};
export const BubbleMenuLinkSelector: FC<Props> = (props) => {
export function BubbleMenuLinkSelector(props: Props) {
const { editor } = props;
// states
const [error, setError] = useState(false);
@ -119,4 +119,4 @@ export const BubbleMenuLinkSelector: FC<Props> = (props) => {
</div>
</FloatingMenuRoot>
);
};
}

View file

@ -30,7 +30,7 @@ type Props = {
editor: Editor;
};
export const BubbleMenuNodeSelector: FC<Props> = (props) => {
export function BubbleMenuNodeSelector(props: Props) {
const { editor } = props;
// floating ui
const { options, getReferenceProps, getFloatingProps } = useFloatingMenu({});
@ -102,4 +102,4 @@ export const BubbleMenuNodeSelector: FC<Props> = (props) => {
</section>
</FloatingMenuRoot>
);
};
}

View file

@ -64,7 +64,7 @@ type Props = {
editor: Editor;
};
export const EditorBubbleMenu: FC<Props> = (props) => {
export function EditorBubbleMenu(props: Props) {
const { editor } = props;
// states
const [isSelecting, setIsSelecting] = useState(false);
@ -223,4 +223,4 @@ export const EditorBubbleMenu: FC<Props> = (props) => {
)}
</BubbleMenu>
);
};
}

View file

@ -14,7 +14,7 @@ type Props = {
options: UseFloatingReturn;
};
export const FloatingMenuRoot: React.FC<Props> = (props) => {
export function FloatingMenuRoot(props: Props) {
const { children, classNames, getFloatingProps, getReferenceProps, menuButton, onClick, options } = props;
// derived values
const { refs, floatingStyles, context } = options;
@ -58,4 +58,4 @@ export const FloatingMenuRoot: React.FC<Props> = (props) => {
)}
</>
);
};
}

View file

@ -19,7 +19,7 @@ export type CustomCalloutNodeViewProps = NodeViewProps & {
updateAttributes: (attrs: Partial<TCalloutBlockAttributes>) => void;
};
export const CustomCalloutBlock: React.FC<CustomCalloutNodeViewProps> = (props) => {
export function CustomCalloutBlock(props: CustomCalloutNodeViewProps) {
const { editor, node, updateAttributes } = props;
// states
const [isEmojiPickerOpen, setIsEmojiPickerOpen] = useState(false);
@ -55,4 +55,4 @@ export const CustomCalloutBlock: React.FC<CustomCalloutNodeViewProps> = (props)
<NodeViewContent as="div" className="w-full break-words" />
</NodeViewWrapper>
);
};
}

View file

@ -12,7 +12,7 @@ type Props = {
toggleDropdown: () => void;
};
export const CalloutBlockColorSelector: React.FC<Props> = (props) => {
export function CalloutBlockColorSelector(props: Props) {
const { disabled, isOpen, onSelect, toggleDropdown } = props;
const handleColorSelect = (val: string | null) => {
@ -73,4 +73,4 @@ export const CalloutBlockColorSelector: React.FC<Props> = (props) => {
</div>
</div>
);
};
}

View file

@ -15,7 +15,7 @@ type Props = {
updateAttributes: (attrs: Partial<TCalloutBlockAttributes>) => void;
};
export const CalloutBlockLogoSelector: React.FC<Props> = (props) => {
export function CalloutBlockLogoSelector(props: Props) {
const { blockAttributes, disabled, handleOpen, isOpen, updateAttributes } = props;
const logoValue: TLogoProps = {
@ -93,4 +93,4 @@ export const CalloutBlockLogoSelector: React.FC<Props> = (props) => {
/>
</div>
);
};
}

View file

@ -19,7 +19,7 @@ type Props = {
node: ProseMirrorNode;
};
export const CodeBlockComponent: React.FC<Props> = ({ node }) => {
export function CodeBlockComponent({ node }: Props) {
const [copied, setCopied] = useState(false);
const copyToClipboard = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
@ -60,4 +60,4 @@ export const CodeBlockComponent: React.FC<Props> = ({ node }) => {
</pre>
</NodeViewWrapper>
);
};
}

View file

@ -20,7 +20,7 @@ type CustomImageBlockProps = CustomImageNodeViewProps & {
downloadSrc: string | undefined;
};
export const CustomImageBlock: React.FC<CustomImageBlockProps> = (props) => {
export function CustomImageBlock(props: CustomImageBlockProps) {
// props
const {
editor,
@ -339,4 +339,4 @@ export const CustomImageBlock: React.FC<CustomImageBlockProps> = (props) => {
</div>
</div>
);
};
}

View file

@ -16,7 +16,7 @@ export type CustomImageNodeViewProps = Omit<NodeViewProps, "extension" | "update
updateAttributes: (attrs: Partial<TCustomImageAttributes>) => void;
};
export const CustomImageNodeView: React.FC<CustomImageNodeViewProps> = (props) => {
export function CustomImageNodeView(props: CustomImageNodeViewProps) {
const { editor, extension, node, updateAttributes } = props;
const { src: imgNodeSrc, status } = node.attrs;
@ -147,4 +147,4 @@ export const CustomImageNodeView: React.FC<CustomImageNodeViewProps> = (props) =
</div>
</NodeViewWrapper>
);
};
}

View file

@ -14,7 +14,7 @@ type Props = {
toggleToolbarViewStatus: (val: boolean) => void;
};
export const ImageAlignmentAction: React.FC<Props> = (props) => {
export function ImageAlignmentAction(props: Props) {
const { activeAlignment, handleChange, isTouchDevice, toggleToolbarViewStatus } = props;
// states
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
@ -61,4 +61,4 @@ export const ImageAlignmentAction: React.FC<Props> = (props) => {
)}
</div>
);
};
}

View file

@ -6,7 +6,7 @@ type Props = {
src: string;
};
export const ImageDownloadAction: React.FC<Props> = (props) => {
export function ImageDownloadAction(props: Props) {
const { src } = props;
return (
@ -21,4 +21,4 @@ export const ImageDownloadAction: React.FC<Props> = (props) => {
</button>
</Tooltip>
);
};
}

View file

@ -20,7 +20,7 @@ type Props = {
width: string;
};
const ImageFullScreenModalWithoutPortal = (props: Props) => {
function ImageFullScreenModalWithoutPortal(props: Props) {
const { aspectRatio, isFullScreenEnabled, isTouchDevice, downloadSrc, src, toggleFullScreenMode, width } = props;
// refs
const dragStart = useRef({ x: 0, y: 0 });
@ -289,9 +289,9 @@ const ImageFullScreenModalWithoutPortal = (props: Props) => {
</div>
</div>
);
};
}
export const ImageFullScreenModal: React.FC<Props> = (props) => {
export function ImageFullScreenModal(props: Props) {
let modal = <ImageFullScreenModalWithoutPortal {...props} />;
const portal = document.querySelector("#editor-portal");
if (portal) {
@ -303,4 +303,4 @@ export const ImageFullScreenModal: React.FC<Props> = (props) => {
}
}
return modal;
};
}

View file

@ -17,7 +17,7 @@ type Props = {
toggleToolbarViewStatus: (val: boolean) => void;
};
export const ImageFullScreenActionRoot: React.FC<Props> = (props) => {
export function ImageFullScreenActionRoot(props: Props) {
const { image, isTouchDevice, toggleToolbarViewStatus } = props;
// states
const [isFullScreenEnabled, setIsFullScreenEnabled] = useState(false);
@ -55,4 +55,4 @@ export const ImageFullScreenActionRoot: React.FC<Props> = (props) => {
</Tooltip>
</>
);
};
}

View file

@ -20,7 +20,7 @@ type Props = {
width: string;
};
export const ImageToolbarRoot: React.FC<Props> = (props) => {
export function ImageToolbarRoot(props: Props) {
const { alignment, editor, downloadSrc, handleAlignmentChange, isTouchDevice } = props;
// states
const [shouldShowToolbar, setShouldShowToolbar] = useState(false);
@ -54,4 +54,4 @@ export const ImageToolbarRoot: React.FC<Props> = (props) => {
</div>
</>
);
};
}

View file

@ -7,7 +7,7 @@ type Props = {
nodeId: string;
};
export const ImageUploadStatus: React.FC<Props> = (props) => {
export function ImageUploadStatus(props: Props) {
const { editor, nodeId } = props;
// Displayed status that will animate smoothly
const [displayStatus, setDisplayStatus] = useState(0);
@ -57,4 +57,4 @@ export const ImageUploadStatus: React.FC<Props> = (props) => {
{displayStatus}%
</div>
);
};
}

View file

@ -23,7 +23,7 @@ type CustomImageUploaderProps = CustomImageNodeViewProps & {
setIsUploaded: (isUploaded: boolean) => void;
};
export const CustomImageUploader = (props: CustomImageUploaderProps) => {
export function CustomImageUploader(props: CustomImageUploaderProps) {
const {
editor,
extension,
@ -247,4 +247,4 @@ export const CustomImageUploader = (props: CustomImageUploaderProps) => {
/>
</div>
);
};
}

View file

@ -20,7 +20,7 @@ type Props = {
isEditable: boolean;
};
export const CustomImageExtension = (props: Props) => {
export function CustomImageExtension(props: Props) {
const { fileHandler, isEditable } = props;
// derived values
const { getAssetSrc, getAssetDownloadSrc, restore: restoreImageFn } = fileHandler;
@ -125,4 +125,4 @@ export const CustomImageExtension = (props: Props) => {
));
},
});
};
}

View file

@ -21,7 +21,10 @@ export type EmojisListDropdownProps = SuggestionProps<EmojiItem, { name: string
forceOpen?: boolean;
};
export const EmojisListDropdown = forwardRef<EmojiListRef, EmojisListDropdownProps>((props, ref) => {
export const EmojisListDropdown = forwardRef(function EmojisListDropdown(
props: EmojisListDropdownProps,
ref: React.ForwardedRef<EmojiListRef>
) {
const { items, command, query, onClose, forceOpen = false } = props;
// states
const [selectedIndex, setSelectedIndex] = useState<number>(0);

View file

@ -24,7 +24,7 @@ type Props = {
fileHandler: TFileHandler;
};
export const ImageExtension = (props: Props) => {
export function ImageExtension(props: Props) {
const { fileHandler } = props;
// derived values
const { getAssetSrc } = fileHandler;
@ -61,4 +61,4 @@ export const ImageExtension = (props: Props) => {
));
},
});
};
}

View file

@ -9,7 +9,7 @@ import { MentionNodeView } from "./mention-node-view";
// utils
import { renderMentionsDropdown } from "./utils";
export const CustomMentionExtension = (props: TMentionHandler) => {
export function CustomMentionExtension(props: TMentionHandler) {
const { searchCallback, renderComponent, getMentionedEntityDetails } = props;
return CustomMentionExtensionConfig.extend({
addOptions(this) {
@ -32,4 +32,4 @@ export const CustomMentionExtension = (props: TMentionHandler) => {
}),
},
});
};
}

View file

@ -12,7 +12,7 @@ export type MentionNodeViewProps = NodeViewProps & {
};
};
export const MentionNodeView: React.FC<MentionNodeViewProps> = (props) => {
export function MentionNodeView(props: MentionNodeViewProps) {
const {
extension,
node: { attrs },
@ -26,4 +26,4 @@ export const MentionNodeView: React.FC<MentionNodeViewProps> = (props) => {
})}
</NodeViewWrapper>
);
};
}

View file

@ -17,7 +17,7 @@ export type MentionsListDropdownProps = SuggestionProps<TMentionSection, TMentio
onClose: () => void;
};
export const MentionsListDropdown = forwardRef((props: MentionsListDropdownProps, ref) => {
export const MentionsListDropdown = forwardRef(function MentionsListDropdown(props: MentionsListDropdownProps, ref) {
const { command, query, searchCallback, onClose } = props;
// states
const [sections, setSections] = useState<TMentionSection[]>([]);

View file

@ -233,6 +233,7 @@ export const getSlashCommandFilteredSections =
title: color.label,
description: "Change text color",
searchTerms: ["color", "text", color.label],
icon: (
<ALargeSmall
className="size-3.5"
@ -241,6 +242,7 @@ export const getSlashCommandFilteredSections =
}}
/>
),
command: ({ editor, range }) => toggleTextColor(color.key, editor, range),
}) as ISlashCommandItem
),
@ -273,10 +275,12 @@ export const getSlashCommandFilteredSections =
description: "Change background color",
searchTerms: ["color", "bg", "background", color.label],
icon: <ALargeSmall className="size-3.5" />,
iconContainerStyle: {
borderRadius: "4px",
backgroundColor: color.backgroundColor,
},
command: ({ editor, range }) => toggleBackgroundColor(color.key, editor, range),
}) as ISlashCommandItem
),

View file

@ -40,7 +40,7 @@ const highlightMatch = (text: string, query: string): React.ReactNode => {
return text;
};
export const CommandMenuItem: React.FC<Props> = (props) => {
export function CommandMenuItem(props: Props) {
const { isSelected, item, itemIndex, onClick, onMouseEnter, sectionIndex, query } = props;
return (
@ -63,4 +63,4 @@ export const CommandMenuItem: React.FC<Props> = (props) => {
{item.badge}
</button>
);
};
}

View file

@ -15,7 +15,7 @@ export type SlashCommandsMenuProps = SuggestionProps<TSlashCommandSection, ISlas
onClose: () => void;
};
export const SlashCommandsMenu = forwardRef((props: SlashCommandsMenuProps, ref) => {
export const SlashCommandsMenu = forwardRef(function SlashCommandsMenu(props: SlashCommandsMenuProps, ref) {
const { items: sections, command, query, onClose } = props;
// states
const [selectedIndex, setSelectedIndex] = useState({

View file

@ -121,9 +121,10 @@ export type TExtensionProps = Pick<IEditorProps, "disabledExtensions" | "flagged
additionalOptions?: TSlashCommandAdditionalOption[];
};
export const SlashCommands = (props: TExtensionProps) =>
Command.configure({
export function SlashCommands(props: TExtensionProps) {
return Command.configure({
suggestion: {
items: getSlashCommandFilteredSections(props),
},
});
}

View file

@ -35,7 +35,7 @@ const handleBackgroundColorChange = (editor: Editor, color: string | null) => {
// .run();
// };
export const TableDragHandleDropdownColorSelector: React.FC<Props> = (props) => {
export function TableDragHandleDropdownColorSelector(props: Props) {
const { editor, onSelect } = props;
return (
@ -115,4 +115,4 @@ export const TableDragHandleDropdownColorSelector: React.FC<Props> = (props) =>
</Disclosure.Panel>
</Disclosure>
);
};
}

View file

@ -45,7 +45,7 @@ export type ColumnDragHandleProps = {
editor: Editor;
};
export const ColumnDragHandle: React.FC<ColumnDragHandleProps> = (props) => {
export function ColumnDragHandle(props: ColumnDragHandleProps) {
const { col, editor } = props;
// states
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
@ -222,4 +222,4 @@ export const ColumnDragHandle: React.FC<ColumnDragHandleProps> = (props) => {
)}
</>
);
};
}

View file

@ -62,7 +62,7 @@ type Props = {
onClose: () => void;
};
export const ColumnOptionsDropdown: React.FC<Props> = (props) => {
export function ColumnOptionsDropdown(props: Props) {
const { editor, onClose } = props;
return (
@ -100,4 +100,4 @@ export const ColumnOptionsDropdown: React.FC<Props> = (props) => {
))}
</>
);
};
}

View file

@ -45,7 +45,7 @@ export type RowDragHandleProps = {
row: number;
};
export const RowDragHandle: React.FC<RowDragHandleProps> = (props) => {
export function RowDragHandle(props: RowDragHandleProps) {
const { editor, row } = props;
// states
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
@ -221,4 +221,4 @@ export const RowDragHandle: React.FC<RowDragHandleProps> = (props) => {
)}
</>
);
};
}

View file

@ -62,7 +62,7 @@ type Props = {
onClose: () => void;
};
export const RowOptionsDropdown: React.FC<Props> = (props) => {
export function RowOptionsDropdown(props: Props) {
const { editor, onClose } = props;
return (
@ -100,4 +100,4 @@ export const RowOptionsDropdown: React.FC<Props> = (props) => {
))}
</>
);
};
}

View file

@ -15,8 +15,8 @@ type Props = {
}) => React.ReactNode;
};
export const WorkItemEmbedExtension = (props: Props) =>
WorkItemEmbedExtensionConfig.extend({
export function WorkItemEmbedExtension(props: Props) {
return WorkItemEmbedExtensionConfig.extend({
addNodeView() {
return ReactNodeViewRenderer((issueProps: NodeViewProps) => (
<NodeViewWrapper>
@ -29,3 +29,4 @@ export const WorkItemEmbedExtension = (props: Props) =>
));
},
});
}

View file

@ -12,7 +12,7 @@ interface TranslationProviderProps {
/**
* Provides the translation store to the application
*/
export const TranslationProvider: React.FC<TranslationProviderProps> = observer(({ children }) => {
export const TranslationProvider = observer(function TranslationProvider({ children }: TranslationProviderProps) {
const [store] = React.useState(() => new TranslationStore());
return <TranslationContext.Provider value={store}>{children}</TranslationContext.Provider>;

View file

@ -30,55 +30,54 @@ export interface AccordionContentProps {
children: React.ReactNode;
}
const AccordionRoot: React.FC<AccordionRootProps> = ({
defaultValue = [],
allowMultiple = false,
className = "",
children,
}) => (
<BaseAccordion.Root defaultValue={defaultValue} openMultiple={allowMultiple} className={`text-base ${className}`}>
{children}
</BaseAccordion.Root>
);
function AccordionRoot({ defaultValue = [], allowMultiple = false, className = "", children }: AccordionRootProps) {
return (
<BaseAccordion.Root defaultValue={defaultValue} openMultiple={allowMultiple} className={`text-base ${className}`}>
{children}
</BaseAccordion.Root>
);
}
const AccordionItem: React.FC<AccordionItemProps> = ({ value, disabled, className = "", children }) => (
<BaseAccordion.Item value={value} disabled={disabled} className={`relative ${className}`}>
{children}
</BaseAccordion.Item>
);
function AccordionItem({ value, disabled, className = "", children }: AccordionItemProps) {
return (
<BaseAccordion.Item value={value} disabled={disabled} className={`relative ${className}`}>
{children}
</BaseAccordion.Item>
);
}
const AccordionTrigger: React.FC<AccordionTriggerProps> = ({
function AccordionTrigger({
className = "",
icon = <PlusIcon aria-hidden="true" className="transition-all ease-out group-data-[panel-open]:rotate-45" />,
iconClassName = "",
children,
asChild = false,
}) => (
<BaseAccordion.Header>
{asChild ? (
<BaseAccordion.Trigger className={`w-full py-2 ${className}`}>{children}</BaseAccordion.Trigger>
) : (
<BaseAccordion.Trigger className={`flex w-full items-center justify-between gap-2 py-2 ${className}`}>
{children}
<span aria-hidden="true" className={`flex-shrink-0 ${iconClassName}`}>
{icon}
</span>
</BaseAccordion.Trigger>
)}
</BaseAccordion.Header>
);
}: AccordionTriggerProps) {
return (
<BaseAccordion.Header>
{asChild ? (
<BaseAccordion.Trigger className={`w-full py-2 ${className}`}>{children}</BaseAccordion.Trigger>
) : (
<BaseAccordion.Trigger className={`flex w-full items-center justify-between gap-2 py-2 ${className}`}>
{children}
<span aria-hidden="true" className={`flex-shrink-0 ${iconClassName}`}>
{icon}
</span>
</BaseAccordion.Trigger>
)}
</BaseAccordion.Header>
);
}
const AccordionContent: React.FC<AccordionContentProps> = ({
className = "",
contentWrapperClassName = "",
children,
}) => (
<BaseAccordion.Panel
className={`h-[var(--accordion-panel-height)] overflow-hidden transition-[height] ease-out data-[ending-style]:h-0 data-[starting-style]:h-0 ${className}`}
>
<div className={`py-2 ${contentWrapperClassName}`}>{children}</div>
</BaseAccordion.Panel>
);
function AccordionContent({ className = "", contentWrapperClassName = "", children }: AccordionContentProps) {
return (
<BaseAccordion.Panel
className={`h-[var(--accordion-panel-height)] overflow-hidden transition-[height] ease-out data-[ending-style]:h-0 data-[starting-style]:h-0 ${className}`}
>
<div className={`py-2 ${contentWrapperClassName}`}>{children}</div>
</BaseAccordion.Panel>
);
}
export const Accordion = {
Root: AccordionRoot,

View file

@ -13,7 +13,7 @@ const sizeClasses = {
lg: "text-base",
};
export const AnimatedCounter: React.FC<AnimatedCounterProps> = ({ count, className, size = "md" }) => {
export function AnimatedCounter({ count, className, size = "md" }: AnimatedCounterProps) {
// states
const [displayCount, setDisplayCount] = useState(count);
const [prevCount, setPrevCount] = useState(count);
@ -92,4 +92,4 @@ export const AnimatedCounter: React.FC<AnimatedCounterProps> = ({ count, classNa
</span>
</div>
);
};
}

View file

@ -79,7 +79,7 @@ export const getBorderRadius = (shape: "circle" | "square") => {
*/
export const isAValidNumber = (value: unknown): value is number => typeof value === "number" && !Number.isNaN(value);
export const Avatar: React.FC<Props> = (props) => {
export function Avatar(props: Props) {
const {
name,
fallbackBackgroundColor,
@ -116,4 +116,4 @@ export const Avatar: React.FC<Props> = (props) => {
</AvatarPrimitive.Root>
</div>
);
};
}

View file

@ -33,99 +33,109 @@ export default meta;
type Story = StoryObj<typeof meta>;
// Sample icons for different variants
const SuccessIcon = () => (
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-green-600"
>
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
<polyline points="22 4 12 14.01 9 11.01" />
</svg>
);
const ErrorIcon = () => (
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-red-600"
>
<circle cx="12" cy="12" r="10" />
<line x1="15" y1="9" x2="9" y2="15" />
<line x1="9" y1="9" x2="15" y2="15" />
</svg>
);
const WarningIcon = () => (
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-yellow-600"
>
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
<line x1="12" y1="9" x2="12" y2="13" />
<line x1="12" y1="17" x2="12.01" y2="17" />
</svg>
);
const InfoIcon = () => (
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-blue-600"
>
<circle cx="12" cy="12" r="10" />
<line x1="12" y1="16" x2="12" y2="12" />
<line x1="12" y1="8" x2="12.01" y2="8" />
</svg>
);
const CloseButton = ({ onClick }: { onClick?: () => void }) => (
<button
onClick={onClick}
className="rounded p-1 hover:bg-black/5 dark:hover:bg-white/5 transition-colors"
aria-label="Dismiss"
>
function SuccessIcon() {
return (
<svg
width="16"
height="16"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-text-secondary"
className="text-green-600"
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
<polyline points="22 4 12 14.01 9 11.01" />
</svg>
</button>
);
);
}
function ErrorIcon() {
return (
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-red-600"
>
<circle cx="12" cy="12" r="10" />
<line x1="15" y1="9" x2="9" y2="15" />
<line x1="9" y1="9" x2="15" y2="15" />
</svg>
);
}
function WarningIcon() {
return (
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-yellow-600"
>
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
<line x1="12" y1="9" x2="12" y2="13" />
<line x1="12" y1="17" x2="12.01" y2="17" />
</svg>
);
}
function InfoIcon() {
return (
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-blue-600"
>
<circle cx="12" cy="12" r="10" />
<line x1="12" y1="16" x2="12" y2="12" />
<line x1="12" y1="8" x2="12.01" y2="8" />
</svg>
);
}
function CloseButton({ onClick }: { onClick?: () => void }) {
return (
<button
onClick={onClick}
className="rounded p-1 hover:bg-black/5 dark:hover:bg-white/5 transition-colors"
aria-label="Dismiss"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-text-secondary"
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
);
}
// ============================================================================
// Interactive Stories

View file

@ -28,102 +28,100 @@ export interface BannerProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
animationDuration?: number;
}
export const Banner = React.forwardRef<HTMLDivElement, BannerProps>(
(
{
icon,
title,
action,
variant = "info",
dismissible = false,
onDismiss,
visible = true,
animationDuration = 200,
className,
children,
...props
},
ref
) => {
// Handle dismissal
const handleDismiss = () => {
if (onDismiss) {
onDismiss();
}
};
// Don't render if not visible
if (!visible) {
return null;
export const Banner = React.forwardRef(function Banner(
{
icon,
title,
action,
variant = "info",
dismissible = false,
onDismiss,
visible = true,
animationDuration = 200,
className,
children,
...props
}: BannerProps,
ref: React.ForwardedRef<HTMLDivElement>
) {
// Handle dismissal
const handleDismiss = () => {
if (onDismiss) {
onDismiss();
}
};
// Get styling using helper functions
const containerStyling = getBannerStyling(variant);
const iconStyling = "flex items-center justify-center flex-shrink-0 size-5";
const titleStyling = getBannerTitleStyling();
const actionStyling = getBannerActionStyling();
const dismissStyling = getBannerDismissStyling();
const dismissIconStyling = getBannerDismissIconStyling();
// Don't render if not visible
if (!visible) {
return null;
}
// Render custom icon component if provided
const renderIcon = () => {
if (icon) {
return <div className={cn(iconStyling)}>{icon}</div>;
}
return null;
};
// Get styling using helper functions
const containerStyling = getBannerStyling(variant);
const iconStyling = "flex items-center justify-center flex-shrink-0 size-5";
const titleStyling = getBannerTitleStyling();
const actionStyling = getBannerActionStyling();
const dismissStyling = getBannerDismissStyling();
const dismissIconStyling = getBannerDismissIconStyling();
// Render dismiss button if dismissible
const renderDismissButton = () => {
if (!dismissible) return null;
// Render custom icon component if provided
const renderIcon = () => {
if (icon) {
return <div className={cn(iconStyling)}>{icon}</div>;
}
return null;
};
return (
<button onClick={handleDismiss} className={cn(dismissStyling)} aria-label="Dismiss banner">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={cn(dismissIconStyling)}
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
);
};
// Render dismiss button if dismissible
const renderDismissButton = () => {
if (!dismissible) return null;
return (
<div
ref={ref}
className={cn(containerStyling, className)}
style={{
transitionDuration: `${animationDuration}ms`,
}}
{...props}
>
{/* Left side: Icon and Title */}
<div className="flex items-center gap-3 flex-1 min-w-0">
{renderIcon()}
{title && <div className={cn(titleStyling)}>{title}</div>}
{children}
</div>
{/* Right side: Actions */}
{(action || dismissible) && (
<div className={cn(actionStyling)}>
{action}
{renderDismissButton()}
</div>
)}
</div>
<button onClick={handleDismiss} className={cn(dismissStyling)} aria-label="Dismiss banner">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={cn(dismissIconStyling)}
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
);
}
);
};
return (
<div
ref={ref}
className={cn(containerStyling, className)}
style={{
transitionDuration: `${animationDuration}ms`,
}}
{...props}
>
{/* Left side: Icon and Title */}
<div className="flex items-center gap-3 flex-1 min-w-0">
{renderIcon()}
{title && <div className={cn(titleStyling)}>{title}</div>}
{children}
</div>
{/* Right side: Actions */}
{(action || dismissible) && (
<div className={cn(actionStyling)}>
{action}
{renderDismissButton()}
</div>
)}
</div>
);
});
Banner.displayName = "Banner";

View file

@ -14,7 +14,7 @@ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
children: React.ReactNode;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
const Button = React.forwardRef(function Button(props: ButtonProps, ref: React.ForwardedRef<HTMLButtonElement>) {
const {
variant = "primary",
size = "md",

View file

@ -8,7 +8,7 @@ import { cn } from "../utils";
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
export const Calendar = ({ className, showOutsideDays = true, ...props }: CalendarProps) => {
export function Calendar({ className, showOutsideDays = true, ...props }: CalendarProps) {
const currentYear = new Date().getFullYear();
const thirtyYearsAgoFirstDay = new Date(currentYear - 30, 0, 1);
const thirtyYearsFromNowFirstDay = new Date(currentYear + 30, 11, 31);
@ -35,4 +35,4 @@ export const Calendar = ({ className, showOutsideDays = true, ...props }: Calend
{...props}
/>
);
};
}

View file

@ -11,7 +11,7 @@ export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
}
const Card = React.forwardRef<HTMLDivElement, CardProps>((props, ref) => {
const Card = React.forwardRef(function Card(props: CardProps, ref: React.ForwardedRef<HTMLDivElement>) {
const {
variant = ECardVariant.WITH_SHADOW,
direction = ECardDirection.COLUMN,

View file

@ -10,7 +10,9 @@ import { getLegendProps } from "../components/legend";
import { CustomXAxisTick, CustomYAxisTick } from "../components/tick";
import { CustomTooltip } from "../components/tooltip";
export const AreaChart = React.memo(<K extends string, T extends string>(props: TAreaChartProps<K, T>) => {
export const AreaChart = React.memo(function AreaChart<K extends string, T extends string>(
props: TAreaChartProps<K, T>
) {
const {
data,
areas,

View file

@ -54,7 +54,7 @@ const getBarPath = (x: number, y: number, width: number, height: number, topRadi
Z
`;
const PercentageText = ({
function PercentageText({
x,
y,
percentage,
@ -64,14 +64,16 @@ const PercentageText = ({
y: number;
percentage: number;
className?: string;
}) => (
<text x={x} y={y} textAnchor="middle" className={cn("text-xs font-medium", className)} fill="currentColor">
{percentage}%
</text>
);
}) {
return (
<text x={x} y={y} textAnchor="middle" className={cn("text-xs font-medium", className)} fill="currentColor">
{percentage}%
</text>
);
}
// Base Components
const CustomBar = React.memo((props: TBarProps) => {
const CustomBar = React.memo(function CustomBar(props: TBarProps) {
const {
opacity,
fill,
@ -118,7 +120,7 @@ const CustomBar = React.memo((props: TBarProps) => {
);
});
const CustomBarLollipop = React.memo((props: TBarProps) => {
const CustomBarLollipop = React.memo(function CustomBarLollipop(props: TBarProps) {
const { fill, x, y, width, height, dataKey, stackKeys, payload, textClassName, showPercentage, dotted } = props;
const currentBarPercentage = calculatePercentage(payload, stackKeys, dataKey);

View file

@ -23,7 +23,7 @@ import { barShapeVariants } from "./bar";
const DEFAULT_BAR_FILL_COLOR = "#000000";
export const BarChart = React.memo(<K extends string, T extends string>(props: TBarChartProps<K, T>) => {
export const BarChart = React.memo(function BarChart<K extends string, T extends string>(props: TBarChartProps<K, T>) {
const {
data,
bars,

View file

@ -31,12 +31,12 @@ export const getLegendProps = (args: TChartLegend): LegendProps => {
};
};
const CustomLegend = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> &
const CustomLegend = React.forwardRef(function CustomLegend(
props: React.ComponentProps<"div"> &
Pick<LegendProps, "payload" | "formatter" | "onClick" | "onMouseEnter" | "onMouseLeave"> &
TChartLegend
>((props, ref) => {
TChartLegend,
ref: React.ForwardedRef<HTMLDivElement>
) {
const { formatter, layout, onClick, onMouseEnter, onMouseLeave, payload } = props;
if (!payload?.length) return null;

View file

@ -4,26 +4,38 @@ import React from "react";
// Common classnames
const AXIS_TICK_CLASSNAME = "fill-custom-text-300 text-sm";
export const CustomXAxisTick = React.memo<any>(({ x, y, payload, getLabel }: any) => (
<g transform={`translate(${x},${y})`}>
<text y={0} dy={16} textAnchor="middle" className={AXIS_TICK_CLASSNAME}>
{getLabel ? getLabel(payload.value) : payload.value}
</text>
</g>
));
export const CustomXAxisTick = React.memo(function CustomXAxisTick({ x, y, payload, getLabel }: any) {
return (
<g transform={`translate(${x},${y})`}>
<text y={0} dy={16} textAnchor="middle" className={AXIS_TICK_CLASSNAME}>
{getLabel ? getLabel(payload.value) : payload.value}
</text>
</g>
);
});
CustomXAxisTick.displayName = "CustomXAxisTick";
export const CustomYAxisTick = React.memo<any>(({ x, y, payload }: any) => (
<g transform={`translate(${x},${y})`}>
<text dx={-10} textAnchor="middle" className={AXIS_TICK_CLASSNAME}>
{payload.value}
</text>
</g>
));
export const CustomYAxisTick = React.memo(function CustomYAxisTick({ x, y, payload }: any) {
return (
<g transform={`translate(${x},${y})`}>
<text dx={-10} textAnchor="middle" className={AXIS_TICK_CLASSNAME}>
{payload.value}
</text>
</g>
);
});
CustomYAxisTick.displayName = "CustomYAxisTick";
export const CustomRadarAxisTick = React.memo<any>(({ x, y, payload, getLabel, cx, cy, offset = 16 }: any) => {
export const CustomRadarAxisTick = React.memo(function CustomRadarAxisTick({
x,
y,
payload,
getLabel,
cx,
cy,
offset = 16,
}: any) {
// Calculate direction vector from center to tick
const dx = x - cx;
const dy = y - cy;

View file

@ -13,7 +13,7 @@ type Props = {
itemDotColors: Record<string, string>;
};
export const CustomTooltip = React.memo((props: Props) => {
export const CustomTooltip = React.memo(function CustomTooltip(props: Props) {
const { active, activeKey, label, payload, itemKeys, itemLabels, itemDotColors } = props;
// derived values
const filteredPayload = payload?.filter((item) => item.dataKey && itemKeys.includes(`${item.dataKey}`));

View file

@ -20,7 +20,9 @@ import { getLegendProps } from "../components/legend";
import { CustomXAxisTick, CustomYAxisTick } from "../components/tick";
import { CustomTooltip } from "../components/tooltip";
export const LineChart = React.memo(<K extends string, T extends string>(props: TLineChartProps<K, T>) => {
export const LineChart = React.memo(function LineChart<K extends string, T extends string>(
props: TLineChartProps<K, T>
) {
const {
data,
lines,

View file

@ -2,7 +2,7 @@ import React from "react";
import { Sector } from "recharts";
import type { PieSectorDataItem } from "recharts/types/polar/Pie";
export const CustomActiveShape = React.memo((props: PieSectorDataItem) => {
export const CustomActiveShape = React.memo(function CustomActiveShape(props: PieSectorDataItem) {
const { cx, cy, cornerRadius, innerRadius, outerRadius, startAngle, endAngle, fill } = props;
return (

View file

@ -9,7 +9,7 @@ import { getLegendProps } from "../components/legend";
import { CustomActiveShape } from "./active-shape";
import { CustomPieChartTooltip } from "./tooltip";
export const PieChart = React.memo(<K extends string, T extends string>(props: TPieChartProps<K, T>) => {
export const PieChart = React.memo(function PieChart<K extends string, T extends string>(props: TPieChartProps<K, T>) {
const {
data,
dataKey,

View file

@ -9,7 +9,7 @@ type Props = {
payload: Payload<ValueType, NameType>[];
};
export const CustomPieChartTooltip = React.memo((props: Props) => {
export const CustomPieChartTooltip = React.memo(function CustomPieChartTooltip(props: Props) {
const { dotColor, label, payload } = props;
return (

View file

@ -13,7 +13,7 @@ import { getLegendProps } from "../components/legend";
import { CustomRadarAxisTick } from "../components/tick";
import { CustomTooltip } from "../components/tooltip";
const RadarChart = <T extends string, K extends string>(props: TRadarChartProps<T, K>) => {
function RadarChart<T extends string, K extends string>(props: TRadarChartProps<T, K>) {
const { data, radars, margin, showTooltip, legend, className, angleAxis } = props;
// states
@ -90,6 +90,6 @@ const RadarChart = <T extends string, K extends string>(props: TRadarChartProps<
</ResponsiveContainer>
</div>
);
};
}
export { RadarChart };

View file

@ -20,7 +20,9 @@ import { getLegendProps } from "../components/legend";
import { CustomXAxisTick, CustomYAxisTick } from "../components/tick";
import { CustomTooltip } from "../components/tooltip";
export const ScatterChart = React.memo(<K extends string, T extends string>(props: TScatterChartProps<K, T>) => {
export const ScatterChart = React.memo(function ScatterChart<K extends string, T extends string>(
props: TScatterChartProps<K, T>
) {
const {
data,
scatterPoints,

View file

@ -146,7 +146,7 @@ const truncateText = (text: string | number, maxWidth: number, fontSize: number,
return `${stringText.slice(0, maxChars - 3)}...`;
};
export const CustomTreeMapContent: React.FC<any> = ({
export function CustomTreeMapContent({
x,
y,
width,
@ -158,7 +158,7 @@ export const CustomTreeMapContent: React.FC<any> = ({
fillClassName,
textClassName,
icon,
}) => {
}: any) {
const dimensions = useMemo(() => {
const pX = x + LAYOUT.PADDING;
const pY = y + LAYOUT.PADDING;
@ -273,4 +273,4 @@ export const CustomTreeMapContent: React.FC<any> = ({
{renderContent()}
</g>
);
};
}

View file

@ -7,7 +7,7 @@ import { cn } from "../../utils/classname";
import { CustomTreeMapContent } from "./map-content";
import { TreeMapTooltip } from "./tooltip";
export const TreeMapChart = React.memo((props: TreeMapChartProps) => {
export const TreeMapChart = React.memo(function TreeMapChart(props: TreeMapChartProps) {
const { data, className = "w-full h-96", isAnimationActive = false, showTooltip = true } = props;
return (
<div className={cn(className)}>

View file

@ -7,7 +7,7 @@ interface TreeMapTooltipProps {
payload: any[] | undefined;
}
export const TreeMapTooltip = React.memo(({ active, payload }: TreeMapTooltipProps) => {
export const TreeMapTooltip = React.memo(function TreeMapTooltip({ active, payload }: TreeMapTooltipProps) {
if (!active || !payload || !payload[0]?.payload) return null;
const data = payload[0].payload;

View file

@ -40,7 +40,7 @@ const useCollapsible = () => {
};
// Components
const Root: React.FC<RootProps> = ({ children, className, isOpen: controlledIsOpen, onToggle, defaultOpen }) => {
function Root({ children, className, isOpen: controlledIsOpen, onToggle, defaultOpen }: RootProps) {
const [localIsOpen, setLocalIsOpen] = useState<boolean>(controlledIsOpen || defaultOpen || false);
useEffect(() => {
@ -69,9 +69,9 @@ const Root: React.FC<RootProps> = ({ children, className, isOpen: controlledIsOp
</BaseCollapsible.Root>
</CollapsibleContext.Provider>
);
};
}
const Trigger: React.FC<TriggerProps> = ({ children, className, buttonRef }) => {
function Trigger({ children, className, buttonRef }: TriggerProps) {
const { isOpen } = useCollapsible();
return (
@ -79,18 +79,20 @@ const Trigger: React.FC<TriggerProps> = ({ children, className, buttonRef }) =>
{children}
</BaseCollapsible.Trigger>
);
};
}
const Content: React.FC<ContentProps> = ({ children, className }) => (
<BaseCollapsible.Panel
className={clsx(
"flex h-[var(--collapsible-panel-height)] flex-col overflow-hidden text-sm transition-all ease-out data-[ending-style]:h-0 data-[starting-style]:h-0",
className
)}
>
{children}
</BaseCollapsible.Panel>
);
function Content({ children, className }: ContentProps) {
return (
<BaseCollapsible.Panel
className={clsx(
"flex h-[var(--collapsible-panel-height)] flex-col overflow-hidden text-sm transition-all ease-out data-[ending-style]:h-0 data-[starting-style]:h-0",
className
)}
>
{children}
</BaseCollapsible.Panel>
);
}
// Compound Component
export const Collapsible = {

View file

@ -90,13 +90,16 @@ function ComboboxRoot({
}
// Trigger button component
const ComboboxButton = React.forwardRef<HTMLButtonElement, ComboboxButtonProps>(
({ className, children, disabled = false }, ref) => (
const ComboboxButton = React.forwardRef(function ComboboxButton(
{ className, children, disabled = false }: ComboboxButtonProps,
ref: React.ForwardedRef<HTMLButtonElement>
) {
return (
<BaseCombobox.Trigger ref={ref} disabled={disabled} className={className}>
{children}
</BaseCombobox.Trigger>
)
);
);
});
// Options popup component
function ComboboxOptions({

View file

@ -24,43 +24,53 @@ export interface ContextMenuItemProps extends React.ComponentProps<typeof Contex
disabled?: boolean;
}
const ContextMenuRoot = React.forwardRef<React.ElementRef<typeof ContextMenuPrimitive.Root>, ContextMenuProps>(
({ children, ...props }, _ref) => <ContextMenuPrimitive.Root {...props}>{children}</ContextMenuPrimitive.Root>
);
const ContextMenuRoot = React.forwardRef(function ContextMenuRoot(
{ children, ...props }: ContextMenuProps,
_ref: React.ForwardedRef<React.ElementRef<typeof ContextMenuPrimitive.Root>>
) {
return <ContextMenuPrimitive.Root {...props}>{children}</ContextMenuPrimitive.Root>;
});
const ContextMenuTrigger = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Trigger>,
ContextMenuTriggerProps
>(({ className, children, ...props }, ref) => (
<ContextMenuPrimitive.Trigger ref={ref} className={cn("outline-none", className)} {...props}>
{children}
</ContextMenuPrimitive.Trigger>
));
const ContextMenuTrigger = React.forwardRef(function ContextMenuTrigger(
{ className, children, ...props }: ContextMenuTriggerProps,
ref: React.ForwardedRef<React.ElementRef<typeof ContextMenuPrimitive.Trigger>>
) {
return (
<ContextMenuPrimitive.Trigger ref={ref} className={cn("outline-none", className)} {...props}>
{children}
</ContextMenuPrimitive.Trigger>
);
});
const ContextMenuPortal = ContextMenuPrimitive.Portal;
const ContextMenuContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Positioner>,
ContextMenuContentProps
>(({ className, children, side = "bottom", sideOffset = 4, ...props }, ref) => (
<ContextMenuPrimitive.Positioner ref={ref} side={side} sideOffset={sideOffset} {...props}>
<ContextMenuPrimitive.Popup
className={cn(
"z-50 min-w-32 overflow-hidden rounded-md border border-custom-border-200 bg-custom-background-100 p-1 shadow-md",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
"data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
"data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
>
{children}
</ContextMenuPrimitive.Popup>
</ContextMenuPrimitive.Positioner>
));
const ContextMenuContent = React.forwardRef(function ContextMenuContent(
{ className, children, side = "bottom", sideOffset = 4, ...props }: ContextMenuContentProps,
ref: React.ForwardedRef<React.ElementRef<typeof ContextMenuPrimitive.Positioner>>
) {
return (
<ContextMenuPrimitive.Positioner ref={ref} side={side} sideOffset={sideOffset} {...props}>
<ContextMenuPrimitive.Popup
className={cn(
"z-50 min-w-32 overflow-hidden rounded-md border border-custom-border-200 bg-custom-background-100 p-1 shadow-md",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
"data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
"data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
>
{children}
</ContextMenuPrimitive.Popup>
</ContextMenuPrimitive.Positioner>
);
});
const ContextMenuItem = React.forwardRef<React.ElementRef<typeof ContextMenuPrimitive.Item>, ContextMenuItemProps>(
({ className, disabled, children, ...props }, ref) => (
const ContextMenuItem = React.forwardRef(function ContextMenuItem(
{ className, disabled, children, ...props }: ContextMenuItemProps,
ref: React.ForwardedRef<React.ElementRef<typeof ContextMenuPrimitive.Item>>
) {
return (
<ContextMenuPrimitive.Item
ref={ref}
className={cn(
@ -74,38 +84,42 @@ const ContextMenuItem = React.forwardRef<React.ElementRef<typeof ContextMenuPrim
>
{children}
</ContextMenuPrimitive.Item>
)
);
);
});
const ContextMenuSeparator = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
React.ComponentProps<typeof ContextMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-custom-border-200", className)}
{...props}
/>
));
const ContextMenuSeparator = React.forwardRef(function ContextMenuSeparator(
{ className, ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Separator>,
ref: React.ForwardedRef<React.ElementRef<typeof ContextMenuPrimitive.Separator>>
) {
return (
<ContextMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-custom-border-200", className)}
{...props}
/>
);
});
const ContextMenuSubmenu = ContextMenuPrimitive.SubmenuRoot;
const ContextMenuSubmenuTrigger = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubmenuTrigger>,
React.ComponentProps<typeof ContextMenuPrimitive.SubmenuTrigger>
>(({ className, children, ...props }, ref) => (
<ContextMenuPrimitive.SubmenuTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:outline-none",
"focus:bg-custom-background-90 data-[state=open]:bg-custom-background-90",
className
)}
{...props}
>
{children}
</ContextMenuPrimitive.SubmenuTrigger>
));
const ContextMenuSubmenuTrigger = React.forwardRef(function ContextMenuSubmenuTrigger(
{ className, children, ...props }: React.ComponentProps<typeof ContextMenuPrimitive.SubmenuTrigger>,
ref: React.ForwardedRef<React.ElementRef<typeof ContextMenuPrimitive.SubmenuTrigger>>
) {
return (
<ContextMenuPrimitive.SubmenuTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:outline-none",
"focus:bg-custom-background-90 data-[state=open]:bg-custom-background-90",
className
)}
{...props}
>
{children}
</ContextMenuPrimitive.SubmenuTrigger>
);
});
ContextMenuRoot.displayName = "ContextMenu";
ContextMenuTrigger.displayName = "ContextMenuTrigger";

View file

@ -47,59 +47,72 @@ const getPositionClassNames = (position: DialogPosition) =>
"top-8 left-1/2 -translate-x-1/2": position === "top",
});
const DialogPortal = memo<React.ComponentProps<typeof BaseDialog.Portal>>(({ children, ...props }) => (
<BaseDialog.Portal data-slot="dialog-portal" {...props}>
{children}
</BaseDialog.Portal>
));
const DialogPortal = memo(function DialogPortal({ children, ...props }) {
return (
<BaseDialog.Portal data-slot="dialog-portal" {...props}>
{children}
</BaseDialog.Portal>
);
});
DialogPortal.displayName = "DialogPortal";
const DialogOverlay = memo<React.ComponentProps<typeof BaseDialog.Backdrop>>(({ className, ...props }) => (
<BaseDialog.Backdrop data-slot="dialog-overlay" className={cn(OVERLAY_CLASSNAME, className)} {...props} />
));
const DialogOverlay = memo(function DialogOverlay({ className, ...props }) {
return <BaseDialog.Backdrop data-slot="dialog-overlay" className={cn(OVERLAY_CLASSNAME, className)} {...props} />;
});
DialogOverlay.displayName = "DialogOverlay";
const DialogComponent = memo<DialogProps>(({ children, ...props }) => (
<BaseDialog.Root data-slot="dialog" {...props}>
{children}
</BaseDialog.Root>
));
const DialogComponent = memo(function DialogComponent({ children, ...props }) {
return (
<BaseDialog.Root data-slot="dialog" {...props}>
{children}
</BaseDialog.Root>
);
});
DialogComponent.displayName = "Dialog";
const DialogTrigger = memo<React.ComponentProps<typeof BaseDialog.Trigger>>(({ children, ...props }) => (
<BaseDialog.Trigger data-slot="dialog-trigger" {...props}>
{children}
</BaseDialog.Trigger>
));
const DialogTrigger = memo(function DialogTrigger({ children, ...props }) {
return (
<BaseDialog.Trigger data-slot="dialog-trigger" {...props}>
{children}
</BaseDialog.Trigger>
);
});
DialogTrigger.displayName = "DialogTrigger";
const DialogPanel = forwardRef<React.ElementRef<typeof BaseDialog.Popup>, DialogPanelProps>(
({ className, width = EDialogWidth.XXL, children, position = "center", ...props }, ref) => {
const positionClassNames = useMemo(() => getPositionClassNames(position), [position]);
return (
<DialogPortal>
<DialogOverlay />
<BaseDialog.Popup
ref={ref}
data-slot="dialog-content"
className={cn(BASE_CLASSNAME, positionClassNames, width, className)}
role="dialog"
aria-modal="true"
{...props}
>
{children}
</BaseDialog.Popup>
</DialogPortal>
);
}
);
const DialogPanel = forwardRef(function DialogPanel(
{ className, width = EDialogWidth.XXL, children, position = "center", ...props }: DialogPanelProps,
ref: React.ForwardedRef<React.ElementRef<typeof BaseDialog.Popup>>
) {
const positionClassNames = useMemo(() => getPositionClassNames(position), [position]);
return (
<DialogPortal>
<DialogOverlay />
<BaseDialog.Popup
ref={ref}
data-slot="dialog-content"
className={cn(BASE_CLASSNAME, positionClassNames, width, className)}
role="dialog"
aria-modal="true"
{...props}
>
{children}
</BaseDialog.Popup>
</DialogPortal>
);
});
DialogPanel.displayName = "DialogPanel";
const DialogTitle = memo<DialogTitleProps>(({ className, children, ...props }) => (
<BaseDialog.Title data-slot="dialog-title" className={cn("text-lg leading-none font-semibold", className)} {...props}>
{children}
</BaseDialog.Title>
));
const DialogTitle = memo(function DialogTitle({ className, children, ...props }) {
return (
<BaseDialog.Title
data-slot="dialog-title"
className={cn("text-lg leading-none font-semibold", className)}
{...props}
>
{children}
</BaseDialog.Title>
);
});
DialogTitle.displayName = "DialogTitle";

View file

@ -8,7 +8,7 @@ import type { TCustomEmojiPicker } from "./helper";
import { emojiToString, EmojiIconPickerTypes } from "./helper";
import { IconRoot } from "./icon/icon-root";
export const EmojiPicker: React.FC<TCustomEmojiPicker> = (props) => {
export function EmojiPicker(props: TCustomEmojiPicker) {
const {
isOpen,
handleToggle,
@ -135,4 +135,4 @@ export const EmojiPicker: React.FC<TCustomEmojiPicker> = (props) => {
</Popover.Panel>
</Popover>
);
};
}

View file

@ -7,7 +7,7 @@ type EmojiRootProps = {
searchDisabled?: boolean;
};
export const EmojiRoot = (props: EmojiRootProps) => {
export function EmojiRoot(props: EmojiRootProps) {
const { onChange, searchPlaceholder = "Search", searchDisabled = false } = props;
return (
<EmojiPicker.Root
@ -63,4 +63,4 @@ export const EmojiRoot = (props: EmojiRootProps) => {
</EmojiPicker.Viewport>
</EmojiPicker.Root>
);
};
}

View file

@ -12,7 +12,7 @@ type IconRootProps = {
iconType: "material" | "lucide";
};
export const IconRoot: React.FC<IconRootProps> = (props) => {
export function IconRoot(props: IconRootProps) {
const { defaultColor, onChange, searchDisabled = false, iconType } = props;
// states
const [activeColor, setActiveColor] = useState(defaultColor);
@ -125,4 +125,4 @@ export const IconRoot: React.FC<IconRootProps> = (props) => {
</div>
</>
);
};
}

View file

@ -7,7 +7,7 @@ type LucideIconsListProps = {
query: string;
};
export const LucideIconsList: React.FC<LucideIconsListProps> = (props) => {
export function LucideIconsList(props: LucideIconsListProps) {
const { query, onChange, activeColor } = props;
const filteredArray = LUCIDE_ICONS_LIST.filter((icon) => icon.name.toLowerCase().includes(query.toLowerCase()));
@ -31,4 +31,4 @@ export const LucideIconsList: React.FC<LucideIconsListProps> = (props) => {
))}
</>
);
};
}

View file

@ -10,7 +10,7 @@ type MaterialIconListProps = {
query: string;
};
export const MaterialIconList: React.FC<MaterialIconListProps> = (props) => {
export function MaterialIconList(props: MaterialIconListProps) {
const { query, onChange, activeColor } = props;
const filteredArray = MATERIAL_ICONS_LIST.filter((icon) => icon.name.toLowerCase().includes(query.toLowerCase()));
@ -52,4 +52,4 @@ export const MaterialIconList: React.FC<MaterialIconListProps> = (props) => {
))}
</>
);
};
}

View file

@ -17,7 +17,7 @@ type Props = {
type?: "lucide" | "material";
};
export const Logo: FC<Props> = (props) => {
export function Logo(props: Props) {
const { logo, size = 16, type = "material" } = props;
// destructuring the logo object
@ -102,4 +102,4 @@ export const Logo: FC<Props> = (props) => {
// if no value, return empty fragment
return <></>;
};
}

View file

@ -22,7 +22,7 @@ export interface EmojiReactionPickerProps {
align?: TAlign;
}
export const EmojiReactionPicker: React.FC<EmojiReactionPickerProps> = (props) => {
export function EmojiReactionPicker(props: EmojiReactionPickerProps) {
const {
isOpen,
handleToggle,
@ -83,4 +83,4 @@ export const EmojiReactionPicker: React.FC<EmojiReactionPickerProps> = (props) =
</Popover.Panel>
</Popover>
);
};
}

View file

@ -36,57 +36,70 @@ export interface EmojiReactionButtonProps extends React.ButtonHTMLAttributes<HTM
className?: string;
}
const EmojiReaction = React.forwardRef<HTMLButtonElement, EmojiReactionProps>(
({ emoji, count, reacted = false, users = [], onReactionClick, className, showCount = true, ...props }, ref) => {
const handleClick = () => {
onReactionClick?.(emoji);
};
const EmojiReaction = React.forwardRef(function EmojiReaction(
{
emoji,
count,
reacted = false,
users = [],
onReactionClick,
className,
showCount = true,
...props
}: EmojiReactionProps,
ref: React.ForwardedRef<HTMLButtonElement>
) {
const handleClick = () => {
onReactionClick?.(emoji);
};
const tooltipContent = React.useMemo(() => {
if (!users.length) return null;
const tooltipContent = React.useMemo(() => {
if (!users.length) return null;
const displayUsers = users.slice(0, 5);
const remainingCount = users.length - displayUsers.length;
const displayUsers = users.slice(0, 5);
const remainingCount = users.length - displayUsers.length;
return (
<div className="text-xs">
<div className="font-medium mb-1">{stringToEmoji(emoji)}</div>
<div>
{displayUsers.join(", ")}
{remainingCount > 0 && ` and ${remainingCount} more`}
</div>
return (
<div className="text-xs">
<div className="font-medium mb-1">{stringToEmoji(emoji)}</div>
<div>
{displayUsers.join(", ")}
{remainingCount > 0 && ` and ${remainingCount} more`}
</div>
);
}, [emoji, users]);
const button = (
<button
ref={ref}
onClick={handleClick}
className={cn(
"inline-flex items-center rounded-full border px-1.5 text-xs gap-0.5 transition-all duration-200",
reacted
? "bg-custom-primary-100/10 border-custom-primary-100 text-custom-primary-100"
: "bg-custom-background-100 border-custom-border-200 text-custom-text-300 hover:border-custom-border-300 hover:bg-custom-background-90",
className
)}
{...props}
>
<span className="text-base leading-unset">{emoji}</span>
{showCount && count > 0 && <AnimatedCounter count={count} size="sm" className="text-xs leading-normal" />}
</button>
</div>
);
}, [emoji, users]);
if (tooltipContent && users.length > 0) {
return <Tooltip tooltipContent={tooltipContent}>{button}</Tooltip>;
}
const button = (
<button
ref={ref}
onClick={handleClick}
className={cn(
"inline-flex items-center rounded-full border px-1.5 text-xs gap-0.5 transition-all duration-200",
reacted
? "bg-custom-primary-100/10 border-custom-primary-100 text-custom-primary-100"
: "bg-custom-background-100 border-custom-border-200 text-custom-text-300 hover:border-custom-border-300 hover:bg-custom-background-90",
className
)}
{...props}
>
<span className="text-base leading-unset">{emoji}</span>
{showCount && count > 0 && <AnimatedCounter count={count} size="sm" className="text-xs leading-normal" />}
</button>
);
return button;
if (tooltipContent && users.length > 0) {
return <Tooltip tooltipContent={tooltipContent}>{button}</Tooltip>;
}
);
const EmojiReactionButton = React.forwardRef<HTMLButtonElement, EmojiReactionButtonProps>(
({ onAddReaction, className, ...props }, ref) => (
return button;
});
const EmojiReactionButton = React.forwardRef(function EmojiReactionButton(
{ onAddReaction, className, ...props }: EmojiReactionButtonProps,
ref: React.ForwardedRef<HTMLButtonElement>
) {
return (
<button
ref={ref}
onClick={onAddReaction}
@ -103,14 +116,22 @@ const EmojiReactionButton = React.forwardRef<HTMLButtonElement, EmojiReactionBut
>
<AddReactionIcon className="h-3 w-3" />
</button>
)
);
);
});
const EmojiReactionGroup = React.forwardRef<HTMLDivElement, EmojiReactionGroupProps>(
(
{ reactions, onReactionClick, onAddReaction, className, showAddButton = true, maxDisplayUsers = 5, ...props },
ref
) => (
const EmojiReactionGroup = React.forwardRef(function EmojiReactionGroup(
{
reactions,
onReactionClick,
onAddReaction,
className,
showAddButton = true,
maxDisplayUsers = 5,
...props
}: EmojiReactionGroupProps,
ref: React.ForwardedRef<HTMLDivElement>
) {
return (
<div ref={ref} className={cn("flex flex-wrap items-center gap-2", className)} {...props}>
{reactions.map((reaction, index) => (
<EmojiReaction
@ -124,8 +145,8 @@ const EmojiReactionGroup = React.forwardRef<HTMLDivElement, EmojiReactionGroupPr
))}
{showAddButton && <EmojiReactionButton onAddReaction={onAddReaction} />}
</div>
)
);
);
});
EmojiReaction.displayName = "EmojiReaction";
EmojiReactionButton.displayName = "EmojiReactionButton";

View file

@ -1,11 +1,54 @@
import { ILLUSTRATION_COLOR_TOKEN_MAP } from "../helper";
import type { TIllustrationAssetProps } from "../helper";
export const CustomerHorizontalStackIllustration = ({ className }: TIllustrationAssetProps) => (
<svg width="81" height="91" viewBox="0 0 81 91" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<g opacity="0.2">
export function CustomerHorizontalStackIllustration({ className }: TIllustrationAssetProps) {
return (
<svg
width="81"
height="91"
viewBox="0 0 81 91"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<g opacity="0.2">
<path
d="M47.8782 2.57309C46.9628 2.10751 45.787 2.14718 44.5087 2.79426L9.267 20.7542C6.37097 22.2299 4.0195 26.2861 4.0195 29.8055V72.6934C4.0195 74.6583 4.74547 76.0394 5.88968 76.6313L2.37019 74.84C1.22598 74.256 0.5 72.867 0.5 70.9021V28.0142C0.5 24.4869 2.85157 20.4386 5.7476 18.9629L40.9893 1.00296C42.2755 0.347996 43.4513 0.316215 44.3588 0.781791L47.8782 2.57309Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.secondary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M47.878 2.57281C49.0222 3.15675 49.7482 4.54577 49.7482 6.51066V49.3986C49.7482 52.9259 47.3966 56.9742 44.5006 58.4498L9.25889 76.4102C7.97264 77.0652 6.79695 77.0966 5.88947 76.631C4.74526 76.0471 4.01929 74.658 4.01929 72.6931V29.8052C4.01929 26.2779 6.37076 22.2296 9.26679 20.754L44.5085 2.79397C45.7947 2.13901 46.9705 2.10723 47.878 2.57281Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.primary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
<g opacity="0.6">
<path
d="M63.2578 9.36801C62.3425 8.90244 61.1667 8.94211 59.8883 9.58918L24.6466 27.5492C21.7506 29.0248 19.3991 33.081 19.3991 36.6004V79.4887C19.3991 81.4536 20.1251 82.8344 21.2693 83.4262L17.7498 81.6349C16.6056 81.0509 15.8796 79.6623 15.8796 77.6974V34.8091C15.8796 31.2818 18.2312 27.2335 21.1272 25.7579L56.3689 7.79788C57.6552 7.14292 58.8309 7.11114 59.7384 7.57671L63.2578 9.36801Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.secondary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M63.2584 9.36578C64.4026 9.94972 65.1285 11.3387 65.1285 13.3036V56.1919C65.1285 59.7192 62.777 63.7672 59.8809 65.2428L24.6394 83.2032C23.3531 83.8581 22.1773 83.8895 21.2698 83.424C20.1256 82.84 19.3997 81.4514 19.3997 79.4865V36.5982C19.3997 33.0709 21.7511 29.0226 24.6472 27.5469L59.8888 9.58694C61.1751 8.93198 62.3509 8.9002 63.2584 9.36578Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.primary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
<path
d="M47.8782 2.57309C46.9628 2.10751 45.787 2.14718 44.5087 2.79426L9.267 20.7542C6.37097 22.2299 4.0195 26.2861 4.0195 29.8055V72.6934C4.0195 74.6583 4.74547 76.0394 5.88968 76.6313L2.37019 74.84C1.22598 74.256 0.5 72.867 0.5 70.9021V28.0142C0.5 24.4869 2.85157 20.4386 5.7476 18.9629L40.9893 1.00296C42.2755 0.347996 43.4513 0.316215 44.3588 0.781791L47.8782 2.57309Z"
d="M78.6299 16.1631C77.7145 15.6975 76.5387 15.7368 75.2604 16.3839L40.0188 34.3442C37.1228 35.8199 34.7712 39.8757 34.7712 43.3951V86.2834C34.7712 88.2483 35.4972 89.6294 36.6414 90.2213L33.122 88.43C31.9778 87.846 31.2517 86.457 31.2517 84.4921V41.6038C31.2517 38.0765 33.6033 34.0286 36.4993 32.5529L71.741 14.5926C73.0272 13.9376 74.203 13.9062 75.1105 14.3718L78.6299 16.1631Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.secondary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
@ -13,53 +56,19 @@ export const CustomerHorizontalStackIllustration = ({ className }: TIllustration
strokeLinejoin="round"
/>
<path
d="M47.878 2.57281C49.0222 3.15675 49.7482 4.54577 49.7482 6.51066V49.3986C49.7482 52.9259 47.3966 56.9742 44.5006 58.4498L9.25889 76.4102C7.97264 77.0652 6.79695 77.0966 5.88947 76.631C4.74526 76.0471 4.01929 74.658 4.01929 72.6931V29.8052C4.01929 26.2779 6.37076 22.2296 9.26679 20.754L44.5085 2.79397C45.7947 2.13901 46.9705 2.10723 47.878 2.57281Z"
d="M78.6299 16.1609C79.7741 16.7448 80.5001 18.1334 80.5001 20.0983V62.9866C80.5001 66.5139 78.1486 70.5622 75.2525 72.0379L40.0109 89.9979C38.7247 90.6528 37.5489 90.6846 36.6414 90.219C35.4972 89.6351 34.7712 88.2461 34.7712 86.2812V43.3929C34.7712 39.8656 37.1228 35.8177 40.0188 34.342L75.2604 16.3816C76.5467 15.7267 77.7225 15.6953 78.6299 16.1609Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.primary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
<g opacity="0.6">
<path
d="M63.2578 9.36801C62.3425 8.90244 61.1667 8.94211 59.8883 9.58918L24.6466 27.5492C21.7506 29.0248 19.3991 33.081 19.3991 36.6004V79.4887C19.3991 81.4536 20.1251 82.8344 21.2693 83.4262L17.7498 81.6349C16.6056 81.0509 15.8796 79.6623 15.8796 77.6974V34.8091C15.8796 31.2818 18.2312 27.2335 21.1272 25.7579L56.3689 7.79788C57.6552 7.14292 58.8309 7.11114 59.7384 7.57671L63.2578 9.36801Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.secondary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
fillRule="evenodd"
clipRule="evenodd"
d="M46.6233 45.5414C46.6233 44.9023 47.0494 44.1683 47.5702 43.9L62.7685 36.1588C63.2893 35.8905 63.7154 36.1904 63.7154 36.8296C63.7154 37.4688 63.2893 38.2027 62.7685 38.471V43.0951L70.3676 39.2205C70.8884 38.9522 71.3145 39.2521 71.3145 39.8913C71.3145 40.5305 70.8884 41.2644 70.3676 41.5327V60.0294L71.3145 59.5482C71.8353 59.2799 72.2615 59.5798 72.2615 60.219C72.2615 60.8582 71.8353 61.5917 71.3145 61.86L46.6154 74.4465C46.0946 74.7148 45.6685 74.4149 45.6685 73.7757C45.6685 73.1365 46.0946 72.4026 46.6154 72.1343L47.5623 71.653V46.22C47.0415 46.4883 46.6154 46.1883 46.6154 45.5492L46.6233 45.5414ZM49.472 45.2417V70.6747L51.3737 69.7041V65.6557C51.3737 64.0617 52.439 62.2233 53.7489 61.5525L56.5977 60.1003C57.9076 59.4296 58.9728 60.1874 58.9728 61.7814V65.8295L60.8746 64.8589V39.4258L49.4799 45.2336L49.472 45.2417ZM62.7764 45.4074V63.9041L68.4738 61V42.5033L62.7764 45.4074ZM57.0712 66.8001V62.752C57.0712 62.4364 56.8581 62.2863 56.5977 62.4126L53.7489 63.8644C53.4885 63.9985 53.2755 64.3695 53.2755 64.6851V68.7332L57.0712 66.8001ZM51.3737 48.9029C51.3737 48.2637 51.7999 47.5298 52.3207 47.2615L53.2676 46.7802C53.7884 46.5119 54.2146 46.8119 54.2146 47.451C54.2146 48.0902 53.7884 48.8242 53.2676 49.0925L52.3207 49.5737C51.7999 49.842 51.3737 49.5421 51.3737 48.9029ZM56.1242 46.4804C56.1242 45.8413 56.5503 45.1073 57.0712 44.839L58.018 44.3578C58.5389 44.0895 58.965 44.3894 58.965 45.0286C58.965 45.6678 58.5389 46.4013 58.018 46.6696L57.0712 47.1513C56.5503 47.4196 56.1242 47.1196 56.1242 46.4804ZM51.3737 53.5273C51.3737 52.8882 51.7999 52.1542 52.3207 51.8859L53.2676 51.4043C53.7884 51.136 54.2146 51.4359 54.2146 52.0751C54.2146 52.7143 53.7884 53.4482 53.2676 53.7165L52.3207 54.1978C51.7999 54.4661 51.3737 54.1665 51.3737 53.5273ZM56.1242 51.1045C56.1242 50.4653 56.5503 49.7314 57.0712 49.4631L58.018 48.9819C58.5389 48.7136 58.965 49.0135 58.965 49.6527C58.965 50.2919 58.5389 51.0258 58.018 51.2941L57.0712 51.7754C56.5503 52.0436 56.1242 51.7437 56.1242 51.1045ZM64.6703 49.0609C64.6703 48.4217 65.0964 47.6877 65.6172 47.4194H65.6251C66.1459 47.1433 66.572 47.4509 66.572 48.0822V48.098C66.572 48.7372 66.1459 49.4711 65.6251 49.7394H65.6172C65.0964 50.0156 64.6703 49.7079 64.6703 49.0767V49.0609ZM51.3737 58.1433C51.3737 57.5042 51.7999 56.7702 52.3207 56.5019L53.2676 56.0207C53.7884 55.7524 54.2146 56.0523 54.2146 56.6915C54.2146 57.3307 53.7884 58.0646 53.2676 58.3329L52.3207 58.8142C51.7999 59.0825 51.3737 58.7825 51.3737 58.1433ZM56.1242 55.7286C56.1242 55.0894 56.5503 54.3559 57.0712 54.0876L58.018 53.606C58.5389 53.3377 58.965 53.6376 58.965 54.2768C58.965 54.916 58.5389 55.6499 58.018 55.9182L57.0712 56.3994C56.5503 56.6677 56.1242 56.3678 56.1242 55.7286ZM64.6703 53.6849C64.6703 53.0458 65.0964 52.3118 65.6172 52.0435H65.6251C66.1459 51.7673 66.572 52.0754 66.572 52.7066V52.7224C66.572 53.3616 66.1459 54.0952 65.6251 54.3635H65.6172C65.0964 54.6397 64.6703 54.332 64.6703 53.7007V53.6849ZM64.6703 58.309C64.6703 57.6698 65.0964 56.9363 65.6172 56.668H65.6251C66.1459 56.3918 66.572 56.6994 66.572 57.3307V57.3465C66.572 57.9857 66.1459 58.7196 65.6251 58.9879H65.6172C65.0964 59.2641 64.6703 58.9561 64.6703 58.3248V58.309Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
/>
<path
d="M63.2584 9.36578C64.4026 9.94972 65.1285 11.3387 65.1285 13.3036V56.1919C65.1285 59.7192 62.777 63.7672 59.8809 65.2428L24.6394 83.2032C23.3531 83.8581 22.1773 83.8895 21.2698 83.424C20.1256 82.84 19.3997 81.4514 19.3997 79.4865V36.5982C19.3997 33.0709 21.7511 29.0226 24.6472 27.5469L59.8888 9.58694C61.1751 8.93198 62.3509 8.9002 63.2584 9.36578Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.primary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
<path
d="M78.6299 16.1631C77.7145 15.6975 76.5387 15.7368 75.2604 16.3839L40.0188 34.3442C37.1228 35.8199 34.7712 39.8757 34.7712 43.3951V86.2834C34.7712 88.2483 35.4972 89.6294 36.6414 90.2213L33.122 88.43C31.9778 87.846 31.2517 86.457 31.2517 84.4921V41.6038C31.2517 38.0765 33.6033 34.0286 36.4993 32.5529L71.741 14.5926C73.0272 13.9376 74.203 13.9062 75.1105 14.3718L78.6299 16.1631Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.secondary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M78.6299 16.1609C79.7741 16.7448 80.5001 18.1334 80.5001 20.0983V62.9866C80.5001 66.5139 78.1486 70.5622 75.2525 72.0379L40.0109 89.9979C38.7247 90.6528 37.5489 90.6846 36.6414 90.219C35.4972 89.6351 34.7712 88.2461 34.7712 86.2812V43.3929C34.7712 39.8656 37.1228 35.8177 40.0188 34.342L75.2604 16.3816C76.5467 15.7267 77.7225 15.6953 78.6299 16.1609Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.primary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M46.6233 45.5414C46.6233 44.9023 47.0494 44.1683 47.5702 43.9L62.7685 36.1588C63.2893 35.8905 63.7154 36.1904 63.7154 36.8296C63.7154 37.4688 63.2893 38.2027 62.7685 38.471V43.0951L70.3676 39.2205C70.8884 38.9522 71.3145 39.2521 71.3145 39.8913C71.3145 40.5305 70.8884 41.2644 70.3676 41.5327V60.0294L71.3145 59.5482C71.8353 59.2799 72.2615 59.5798 72.2615 60.219C72.2615 60.8582 71.8353 61.5917 71.3145 61.86L46.6154 74.4465C46.0946 74.7148 45.6685 74.4149 45.6685 73.7757C45.6685 73.1365 46.0946 72.4026 46.6154 72.1343L47.5623 71.653V46.22C47.0415 46.4883 46.6154 46.1883 46.6154 45.5492L46.6233 45.5414ZM49.472 45.2417V70.6747L51.3737 69.7041V65.6557C51.3737 64.0617 52.439 62.2233 53.7489 61.5525L56.5977 60.1003C57.9076 59.4296 58.9728 60.1874 58.9728 61.7814V65.8295L60.8746 64.8589V39.4258L49.4799 45.2336L49.472 45.2417ZM62.7764 45.4074V63.9041L68.4738 61V42.5033L62.7764 45.4074ZM57.0712 66.8001V62.752C57.0712 62.4364 56.8581 62.2863 56.5977 62.4126L53.7489 63.8644C53.4885 63.9985 53.2755 64.3695 53.2755 64.6851V68.7332L57.0712 66.8001ZM51.3737 48.9029C51.3737 48.2637 51.7999 47.5298 52.3207 47.2615L53.2676 46.7802C53.7884 46.5119 54.2146 46.8119 54.2146 47.451C54.2146 48.0902 53.7884 48.8242 53.2676 49.0925L52.3207 49.5737C51.7999 49.842 51.3737 49.5421 51.3737 48.9029ZM56.1242 46.4804C56.1242 45.8413 56.5503 45.1073 57.0712 44.839L58.018 44.3578C58.5389 44.0895 58.965 44.3894 58.965 45.0286C58.965 45.6678 58.5389 46.4013 58.018 46.6696L57.0712 47.1513C56.5503 47.4196 56.1242 47.1196 56.1242 46.4804ZM51.3737 53.5273C51.3737 52.8882 51.7999 52.1542 52.3207 51.8859L53.2676 51.4043C53.7884 51.136 54.2146 51.4359 54.2146 52.0751C54.2146 52.7143 53.7884 53.4482 53.2676 53.7165L52.3207 54.1978C51.7999 54.4661 51.3737 54.1665 51.3737 53.5273ZM56.1242 51.1045C56.1242 50.4653 56.5503 49.7314 57.0712 49.4631L58.018 48.9819C58.5389 48.7136 58.965 49.0135 58.965 49.6527C58.965 50.2919 58.5389 51.0258 58.018 51.2941L57.0712 51.7754C56.5503 52.0436 56.1242 51.7437 56.1242 51.1045ZM64.6703 49.0609C64.6703 48.4217 65.0964 47.6877 65.6172 47.4194H65.6251C66.1459 47.1433 66.572 47.4509 66.572 48.0822V48.098C66.572 48.7372 66.1459 49.4711 65.6251 49.7394H65.6172C65.0964 50.0156 64.6703 49.7079 64.6703 49.0767V49.0609ZM51.3737 58.1433C51.3737 57.5042 51.7999 56.7702 52.3207 56.5019L53.2676 56.0207C53.7884 55.7524 54.2146 56.0523 54.2146 56.6915C54.2146 57.3307 53.7884 58.0646 53.2676 58.3329L52.3207 58.8142C51.7999 59.0825 51.3737 58.7825 51.3737 58.1433ZM56.1242 55.7286C56.1242 55.0894 56.5503 54.3559 57.0712 54.0876L58.018 53.606C58.5389 53.3377 58.965 53.6376 58.965 54.2768C58.965 54.916 58.5389 55.6499 58.018 55.9182L57.0712 56.3994C56.5503 56.6677 56.1242 56.3678 56.1242 55.7286ZM64.6703 53.6849C64.6703 53.0458 65.0964 52.3118 65.6172 52.0435H65.6251C66.1459 51.7673 66.572 52.0754 66.572 52.7066V52.7224C66.572 53.3616 66.1459 54.0952 65.6251 54.3635H65.6172C65.0964 54.6397 64.6703 54.332 64.6703 53.7007V53.6849ZM64.6703 58.309C64.6703 57.6698 65.0964 56.9363 65.6172 56.668H65.6251C66.1459 56.3918 66.572 56.6994 66.572 57.3307V57.3465C66.572 57.9857 66.1459 58.7196 65.6251 58.9879H65.6172C65.0964 59.2641 64.6703 58.9561 64.6703 58.3248V58.309Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
/>
</svg>
);
</svg>
);
}

View file

@ -1,11 +1,54 @@
import { ILLUSTRATION_COLOR_TOKEN_MAP } from "../helper";
import type { TIllustrationAssetProps } from "../helper";
export const EpicHorizontalStackIllustration = ({ className }: TIllustrationAssetProps) => (
<svg width="81" height="92" viewBox="0 0 81 92" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<g opacity="0.2">
export function EpicHorizontalStackIllustration({ className }: TIllustrationAssetProps) {
return (
<svg
width="81"
height="92"
viewBox="0 0 81 92"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<g opacity="0.2">
<path
d="M47.8729 3.07316C46.9576 2.60763 45.782 2.6473 44.5038 3.2943L9.26602 21.2523C6.37031 22.7277 4.01901 26.7835 4.01901 30.3025V73.1855C4.01901 75.1502 4.7449 76.5312 5.88899 77.1229L2.36998 75.3318C1.2259 74.748 0.5 73.3595 0.5 71.3948V28.5114C0.5 24.9845 2.8513 20.9366 5.74701 19.4612L40.9848 1.5032C42.2709 0.848314 43.4464 0.816538 44.3538 1.28206L47.8729 3.07316Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.secondary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M47.8722 3.07317C49.0163 3.65704 49.7422 5.04591 49.7422 7.01057V49.894C49.7422 53.4209 47.3909 57.4684 44.4952 58.9439L9.25743 76.9022C7.97132 77.5571 6.79566 77.5885 5.88829 77.1229C4.74421 76.5391 4.01831 75.1502 4.01831 73.1855V30.3025C4.01831 26.7756 6.36962 22.7277 9.26533 21.2523L44.5031 3.29431C45.7892 2.63942 46.9648 2.60764 47.8722 3.07317Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.primary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
<g opacity="0.6">
<path
d="M63.2511 9.86669C62.3358 9.40117 61.1602 9.44084 59.8819 10.0878L24.6442 28.0458C21.7485 29.5213 19.3972 33.577 19.3972 37.096V79.9795C19.3972 81.9441 20.1231 83.3247 21.2672 83.9165L17.7482 82.1254C16.6041 81.5415 15.8782 80.153 15.8782 78.1884V35.3049C15.8782 31.778 18.2295 27.7302 21.1252 26.2547L56.3629 8.29674C57.649 7.64185 58.8246 7.61046 59.732 8.07598L63.2511 9.86669Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.secondary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M63.2519 9.86653C64.3959 10.4504 65.1218 11.8393 65.1218 13.8039V56.6874C65.1218 60.2143 62.7705 64.2617 59.8748 65.7372L24.6371 83.6956C23.351 84.3504 22.1753 84.3818 21.2679 83.9163C20.1238 83.3324 19.3979 81.944 19.3979 79.9793V37.0959C19.3979 33.569 21.7493 29.5211 24.645 28.0456L59.8827 10.0877C61.1688 9.43279 62.3445 9.40101 63.2519 9.86653Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.primary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
<path
d="M47.8729 3.07316C46.9576 2.60763 45.782 2.6473 44.5038 3.2943L9.26602 21.2523C6.37031 22.7277 4.01901 26.7835 4.01901 30.3025V73.1855C4.01901 75.1502 4.7449 76.5312 5.88899 77.1229L2.36998 75.3318C1.2259 74.748 0.5 73.3595 0.5 71.3948V28.5114C0.5 24.9845 2.8513 20.9366 5.74701 19.4612L40.9848 1.5032C42.2709 0.848314 43.4464 0.816538 44.3538 1.28206L47.8729 3.07316Z"
d="M78.6293 16.668C77.714 16.2025 76.5383 16.2421 75.2601 16.8891L40.0224 34.8471C37.1267 36.3225 34.7755 40.3783 34.7755 43.8973V86.7807C34.7755 88.7454 35.5013 90.126 36.6454 90.7178L33.1263 88.9267C31.9822 88.3428 31.2563 86.9543 31.2563 84.9896V42.1062C31.2563 38.5793 33.6077 34.5314 36.5034 33.056L71.7411 15.098C73.0272 14.4431 74.2029 14.4114 75.1102 14.8769L78.6293 16.668Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.secondary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
@ -13,51 +56,17 @@ export const EpicHorizontalStackIllustration = ({ className }: TIllustrationAsse
strokeLinejoin="round"
/>
<path
d="M47.8722 3.07317C49.0163 3.65704 49.7422 5.04591 49.7422 7.01057V49.894C49.7422 53.4209 47.3909 57.4684 44.4952 58.9439L9.25743 76.9022C7.97132 77.5571 6.79566 77.5885 5.88829 77.1229C4.74421 76.5391 4.01831 75.1502 4.01831 73.1855V30.3025C4.01831 26.7756 6.36962 22.7277 9.26533 21.2523L44.5031 3.29431C45.7892 2.63942 46.9648 2.60764 47.8722 3.07317Z"
d="M78.6299 16.668C79.774 17.2519 80.4999 18.6407 80.4999 20.6054V63.4888C80.4999 67.0157 78.1486 71.0632 75.2529 72.5387L40.0151 90.497C38.729 91.1519 37.5535 91.1833 36.6461 90.7178C35.502 90.1339 34.7761 88.7454 34.7761 86.7807V43.8973C34.7761 40.3704 37.1273 36.3225 40.023 34.8471L75.2608 16.8891C76.5469 16.2342 77.7225 16.2025 78.6299 16.668Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.primary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
<g opacity="0.6">
<path
d="M63.2511 9.86669C62.3358 9.40117 61.1602 9.44084 59.8819 10.0878L24.6442 28.0458C21.7485 29.5213 19.3972 33.577 19.3972 37.096V79.9795C19.3972 81.9441 20.1231 83.3247 21.2672 83.9165L17.7482 82.1254C16.6041 81.5415 15.8782 80.153 15.8782 78.1884V35.3049C15.8782 31.778 18.2295 27.7302 21.1252 26.2547L56.3629 8.29674C57.649 7.64185 58.8246 7.61046 59.732 8.07598L63.2511 9.86669Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.secondary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
d="M73.7365 51.6298V55.4643C73.7365 55.8589 73.6103 56.2928 73.4051 56.6795C73.1842 57.0661 72.9001 57.3741 72.5924 57.524L47.4226 70.3533C46.7914 70.6768 46.2786 70.2743 46.2786 69.4537V63.3469L53.9084 46.7774C54.3345 46.0042 54.8315 45.3175 55.4391 45.0098C56.0466 44.7021 56.5359 44.8836 56.962 45.2229L63.5582 54.6044L66.4065 50.0441C66.8326 49.2708 67.4164 48.663 68.024 48.3473C68.6315 48.0317 69.2154 48.0555 69.6336 48.3947L73.7365 51.6221V51.6298Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
/>
<path
d="M63.2519 9.86653C64.3959 10.4504 65.1218 11.8393 65.1218 13.8039V56.6874C65.1218 60.2143 62.7705 64.2617 59.8748 65.7372L24.6371 83.6956C23.351 84.3504 22.1753 84.3818 21.2679 83.9163C20.1238 83.3324 19.3979 81.944 19.3979 79.9793V37.0959C19.3979 33.569 21.7493 29.5211 24.645 28.0456L59.8827 10.0877C61.1688 9.43279 62.3445 9.40101 63.2519 9.86653Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.primary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
<path
d="M78.6293 16.668C77.714 16.2025 76.5383 16.2421 75.2601 16.8891L40.0224 34.8471C37.1267 36.3225 34.7755 40.3783 34.7755 43.8973V86.7807C34.7755 88.7454 35.5013 90.126 36.6454 90.7178L33.1263 88.9267C31.9822 88.3428 31.2563 86.9543 31.2563 84.9896V42.1062C31.2563 38.5793 33.6077 34.5314 36.5034 33.056L71.7411 15.098C73.0272 14.4431 74.2029 14.4114 75.1102 14.8769L78.6293 16.668Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.secondary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M78.6299 16.668C79.774 17.2519 80.4999 18.6407 80.4999 20.6054V63.4888C80.4999 67.0157 78.1486 71.0632 75.2529 72.5387L40.0151 90.497C38.729 91.1519 37.5535 91.1833 36.6461 90.7178C35.502 90.1339 34.7761 88.7454 34.7761 86.7807V43.8973C34.7761 40.3704 37.1273 36.3225 40.023 34.8471L75.2608 16.8891C76.5469 16.2342 77.7225 16.2025 78.6299 16.668Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.primary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M73.7365 51.6298V55.4643C73.7365 55.8589 73.6103 56.2928 73.4051 56.6795C73.1842 57.0661 72.9001 57.3741 72.5924 57.524L47.4226 70.3533C46.7914 70.6768 46.2786 70.2743 46.2786 69.4537V63.3469L53.9084 46.7774C54.3345 46.0042 54.8315 45.3175 55.4391 45.0098C56.0466 44.7021 56.5359 44.8836 56.962 45.2229L63.5582 54.6044L66.4065 50.0441C66.8326 49.2708 67.4164 48.663 68.024 48.3473C68.6315 48.0317 69.2154 48.0555 69.6336 48.3947L73.7365 51.6221V51.6298Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
/>
</svg>
);
</svg>
);
}

View file

@ -1,11 +1,54 @@
import { ILLUSTRATION_COLOR_TOKEN_MAP } from "../helper";
import type { TIllustrationAssetProps } from "../helper";
export const EstimateHorizontalStackIllustration = ({ className }: TIllustrationAssetProps) => (
<svg width="81" height="92" viewBox="0 0 81 92" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<g opacity="0.2">
export function EstimateHorizontalStackIllustration({ className }: TIllustrationAssetProps) {
return (
<svg
width="81"
height="92"
viewBox="0 0 81 92"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<g opacity="0.2">
<path
d="M47.8734 3.07267C46.9581 2.60715 45.7825 2.6466 44.5042 3.2936L9.26616 21.2519C6.37042 22.7274 4.01909 26.783 4.01909 30.3021V73.1858C4.01909 75.1505 4.745 76.5313 5.88909 77.1231L2.37005 75.332C1.22595 74.7481 0.5 73.3594 0.5 71.3948V28.511C0.5 24.984 2.85133 20.9363 5.74707 19.4608L40.9851 1.5025C42.2713 0.847607 43.4469 0.816046 44.3543 1.28157L47.8734 3.07267Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.secondary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M47.8732 3.07177C49.0173 3.65565 49.7432 5.04434 49.7432 7.00903V49.8928C49.7432 53.4197 47.3919 57.4675 44.4962 58.943L9.25809 76.9013C7.97197 77.5562 6.7963 77.5877 5.88892 77.1222C4.74482 76.5383 4.01892 75.1496 4.01892 73.1849V30.3012C4.01892 26.7742 6.37025 22.7265 9.26599 21.251L44.5041 3.29269C45.7902 2.6378 46.9658 2.60624 47.8732 3.07177Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.primary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
<g opacity="0.6">
<path
d="M63.2514 9.86595C62.3361 9.40042 61.1605 9.43987 59.8822 10.0869L24.6442 28.0452C21.7484 29.5207 19.3971 33.5763 19.3971 37.0954V79.9791C19.3971 81.9438 20.123 83.3246 21.2671 83.9164L17.748 82.1253C16.604 81.5414 15.8781 80.1527 15.8781 78.188V35.3043C15.8781 31.7773 18.2293 27.7296 21.1251 26.2541L56.3632 8.29578C57.6493 7.64088 58.8249 7.60932 59.7323 8.07485L63.2514 9.86595Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.secondary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M63.2514 9.86614C64.3955 10.45 65.1214 11.8387 65.1214 13.8034V56.6872C65.1214 60.2141 62.7701 64.2618 59.8743 65.7373L24.6363 83.6956C23.3501 84.3505 22.1745 84.3821 21.2671 83.9166C20.123 83.3327 19.3971 81.944 19.3971 79.9793V37.0955C19.3971 33.5686 21.7484 29.5209 24.6442 28.0454L59.8822 10.0871C61.1684 9.43217 62.344 9.40061 63.2514 9.86614Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.primary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
<path
d="M47.8734 3.07267C46.9581 2.60715 45.7825 2.6466 44.5042 3.2936L9.26616 21.2519C6.37042 22.7274 4.01909 26.783 4.01909 30.3021V73.1858C4.01909 75.1505 4.745 76.5313 5.88909 77.1231L2.37005 75.332C1.22595 74.7481 0.5 73.3594 0.5 71.3948V28.511C0.5 24.984 2.85133 20.9363 5.74707 19.4608L40.9851 1.5025C42.2713 0.847607 43.4469 0.816046 44.3543 1.28157L47.8734 3.07267Z"
d="M78.6301 16.6678C77.7148 16.2023 76.5391 16.2417 75.2609 16.8887L40.0228 34.8471C37.1271 36.3225 34.7758 40.3781 34.7758 43.8972V86.781C34.7758 88.7457 35.5017 90.1265 36.6458 90.7182L33.1267 88.9271C31.9826 88.3433 31.2567 86.9546 31.2567 84.9899V42.1061C31.2567 38.5792 33.608 34.5315 36.5037 33.056L71.7419 15.0977C73.028 14.4428 74.2036 14.4112 75.111 14.8767L78.6301 16.6678Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.secondary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
@ -13,53 +56,19 @@ export const EstimateHorizontalStackIllustration = ({ className }: TIllustration
strokeLinejoin="round"
/>
<path
d="M47.8732 3.07177C49.0173 3.65565 49.7432 5.04434 49.7432 7.00903V49.8928C49.7432 53.4197 47.3919 57.4675 44.4962 58.943L9.25809 76.9013C7.97197 77.5562 6.7963 77.5877 5.88892 77.1222C4.74482 76.5383 4.01892 75.1496 4.01892 73.1849V30.3012C4.01892 26.7742 6.37025 22.7265 9.26599 21.251L44.5041 3.29269C45.7902 2.6378 46.9658 2.60624 47.8732 3.07177Z"
d="M78.6299 16.668C79.774 17.2519 80.4999 18.6406 80.4999 20.6053V63.489C80.4999 67.016 78.1487 71.0637 75.2529 72.5392L40.0148 90.4975C38.7287 91.1524 37.553 91.184 36.6456 90.7184C35.5015 90.1346 34.7756 88.7459 34.7756 86.7812V43.8974C34.7756 40.3705 37.127 36.3227 40.0227 34.8473L75.2608 16.8889C76.5469 16.2341 77.7226 16.2025 78.6299 16.668Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.primary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
<g opacity="0.6">
<path
d="M63.2514 9.86595C62.3361 9.40042 61.1605 9.43987 59.8822 10.0869L24.6442 28.0452C21.7484 29.5207 19.3971 33.5763 19.3971 37.0954V79.9791C19.3971 81.9438 20.123 83.3246 21.2671 83.9164L17.748 82.1253C16.604 81.5414 15.8781 80.1527 15.8781 78.188V35.3043C15.8781 31.7773 18.2293 27.7296 21.1251 26.2541L56.3632 8.29578C57.6493 7.64088 58.8249 7.60932 59.7323 8.07485L63.2514 9.86595Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.secondary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
fillRule="evenodd"
clipRule="evenodd"
d="M57.2395 40.7963C57.7524 40.1808 58.3283 39.6916 58.9201 39.3918C59.5119 39.092 60.0879 38.9815 60.6008 39.0841C61.1136 39.1788 61.5397 39.4786 61.8316 39.952L70.8029 54.4938C71.0949 54.9673 71.2527 55.5906 71.2527 56.3086C71.2527 57.0266 71.0949 57.8156 70.8029 58.5889C70.511 59.3621 70.0849 60.0959 69.572 60.7193C69.0592 61.3426 68.4832 61.8239 67.8914 62.1238L49.9331 71.2686C49.3413 71.5684 48.7653 71.671 48.2524 71.5763C47.7395 71.4738 47.3135 71.1739 47.0215 70.7005C46.7296 70.2271 46.5718 69.6038 46.5718 68.8857C46.5718 68.1677 46.7296 67.3866 47.0215 66.6133L56.0007 42.9188C56.3005 42.1455 56.7187 41.4117 57.2316 40.7963H57.2395ZM58.9201 42.1218C58.7229 42.2244 58.5335 42.3822 58.3599 42.5874C58.1942 42.7925 58.0522 43.0371 57.9496 43.2896L48.9705 66.9763C48.8758 67.2367 48.8205 67.4971 48.8205 67.7338C48.8205 67.9705 48.8758 68.1835 48.9705 68.3413C49.0651 68.4991 49.2071 68.6017 49.3807 68.6332C49.5543 68.6648 49.7437 68.6332 49.941 68.5307L67.8914 59.3858C68.0886 59.2832 68.278 59.1254 68.4516 58.9203C68.6252 58.7151 68.7672 58.4706 68.8619 58.2102C68.9566 57.9498 69.0118 57.6894 69.0118 57.4527C69.0118 57.216 68.9566 57.0029 68.8619 56.8451L59.8906 42.3033C59.7959 42.1455 59.6539 42.0508 59.4803 42.0193C59.3067 41.9877 59.1174 42.0193 58.9201 42.1218Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
/>
<path
d="M63.2514 9.86614C64.3955 10.45 65.1214 11.8387 65.1214 13.8034V56.6872C65.1214 60.2141 62.7701 64.2618 59.8743 65.7373L24.6363 83.6956C23.3501 84.3505 22.1745 84.3821 21.2671 83.9166C20.123 83.3327 19.3971 81.944 19.3971 79.9793V37.0955C19.3971 33.5686 21.7484 29.5209 24.6442 28.0454L59.8822 10.0871C61.1684 9.43217 62.344 9.40061 63.2514 9.86614Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.primary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
<path
d="M78.6301 16.6678C77.7148 16.2023 76.5391 16.2417 75.2609 16.8887L40.0228 34.8471C37.1271 36.3225 34.7758 40.3781 34.7758 43.8972V86.781C34.7758 88.7457 35.5017 90.1265 36.6458 90.7182L33.1267 88.9271C31.9826 88.3433 31.2567 86.9546 31.2567 84.9899V42.1061C31.2567 38.5792 33.608 34.5315 36.5037 33.056L71.7419 15.0977C73.028 14.4428 74.2036 14.4112 75.111 14.8767L78.6301 16.6678Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.secondary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M78.6299 16.668C79.774 17.2519 80.4999 18.6406 80.4999 20.6053V63.489C80.4999 67.016 78.1487 71.0637 75.2529 72.5392L40.0148 90.4975C38.7287 91.1524 37.553 91.184 36.6456 90.7184C35.5015 90.1346 34.7756 88.7459 34.7756 86.7812V43.8974C34.7756 40.3705 37.127 36.3227 40.0227 34.8473L75.2608 16.8889C76.5469 16.2341 77.7226 16.2025 78.6299 16.668Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.primary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M57.2395 40.7963C57.7524 40.1808 58.3283 39.6916 58.9201 39.3918C59.5119 39.092 60.0879 38.9815 60.6008 39.0841C61.1136 39.1788 61.5397 39.4786 61.8316 39.952L70.8029 54.4938C71.0949 54.9673 71.2527 55.5906 71.2527 56.3086C71.2527 57.0266 71.0949 57.8156 70.8029 58.5889C70.511 59.3621 70.0849 60.0959 69.572 60.7193C69.0592 61.3426 68.4832 61.8239 67.8914 62.1238L49.9331 71.2686C49.3413 71.5684 48.7653 71.671 48.2524 71.5763C47.7395 71.4738 47.3135 71.1739 47.0215 70.7005C46.7296 70.2271 46.5718 69.6038 46.5718 68.8857C46.5718 68.1677 46.7296 67.3866 47.0215 66.6133L56.0007 42.9188C56.3005 42.1455 56.7187 41.4117 57.2316 40.7963H57.2395ZM58.9201 42.1218C58.7229 42.2244 58.5335 42.3822 58.3599 42.5874C58.1942 42.7925 58.0522 43.0371 57.9496 43.2896L48.9705 66.9763C48.8758 67.2367 48.8205 67.4971 48.8205 67.7338C48.8205 67.9705 48.8758 68.1835 48.9705 68.3413C49.0651 68.4991 49.2071 68.6017 49.3807 68.6332C49.5543 68.6648 49.7437 68.6332 49.941 68.5307L67.8914 59.3858C68.0886 59.2832 68.278 59.1254 68.4516 58.9203C68.6252 58.7151 68.7672 58.4706 68.8619 58.2102C68.9566 57.9498 69.0118 57.6894 69.0118 57.4527C69.0118 57.216 68.9566 57.0029 68.8619 56.8451L59.8906 42.3033C59.7959 42.1455 59.6539 42.0508 59.4803 42.0193C59.3067 41.9877 59.1174 42.0193 58.9201 42.1218Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
/>
</svg>
);
</svg>
);
}

View file

@ -1,11 +1,54 @@
import { ILLUSTRATION_COLOR_TOKEN_MAP } from "../helper";
import type { TIllustrationAssetProps } from "../helper";
export const ExportHorizontalStackIllustration = ({ className }: TIllustrationAssetProps) => (
<svg width="71" height="81" viewBox="0 0 71 81" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<g opacity="0.2">
export function ExportHorizontalStackIllustration({ className }: TIllustrationAssetProps) {
return (
<svg
width="71"
height="81"
viewBox="0 0 71 81"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<g opacity="0.2">
<path
d="M41.9519 2.94243C41.151 2.53509 40.1223 2.5698 39.0038 3.13593L8.17037 18.8494C5.63659 20.1404 3.57921 23.6892 3.57921 26.7684V64.2916C3.57921 66.0107 4.21438 67.2191 5.21547 67.7369L2.13625 66.1696C1.13517 65.6587 0.5 64.4435 0.5 62.7244V25.2012C0.5 22.1151 2.55742 18.5732 5.0912 17.2821L35.9246 1.5687C37.05 0.995669 38.0787 0.967864 38.8726 1.3752L41.9519 2.94243Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.secondary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.4"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M41.9519 2.94356C42.9529 3.45446 43.5881 4.66973 43.5881 6.38884V43.912C43.5881 46.9981 41.5307 50.54 38.9969 51.8311L8.16351 67.5448C7.03816 68.1179 6.00944 68.1453 5.21548 67.738C4.21439 67.2271 3.57922 66.0118 3.57922 64.2927V26.7696C3.57922 23.6835 5.6366 20.1415 8.17038 18.8505L39.0038 3.13706C40.1292 2.56403 41.1579 2.53622 41.9519 2.94356Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.primary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.4"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
<g opacity="0.6">
<path
d="M55.4078 8.88579C54.6069 8.47845 53.5782 8.51316 52.4597 9.07929L21.6263 24.7927C19.0926 26.0838 17.0351 29.6326 17.0351 32.7118V70.2353C17.0351 71.9544 17.6703 73.1624 18.6714 73.6802L15.5922 72.113C14.5911 71.6021 13.9559 70.3872 13.9559 68.6681V31.1446C13.9559 28.0585 16.0134 24.5165 18.5471 23.2255L49.3805 7.51206C50.5059 6.93903 51.5346 6.91122 52.3286 7.31856L55.4078 8.88579Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.secondary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.4"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M55.4079 8.88692C56.4089 9.39782 57.0441 10.6131 57.0441 12.3322V49.8557C57.0441 52.9418 54.9867 56.4834 52.4529 57.7744L21.6195 73.4882C20.4941 74.0612 19.4654 74.0887 18.6715 73.6814C17.6704 73.1704 17.0352 71.9555 17.0352 70.2364V32.7129C17.0352 29.6268 19.0926 26.0849 21.6264 24.7938L52.4598 9.08042C53.5852 8.50739 54.6139 8.47958 55.4079 8.88692Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.primary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.4"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
<path
d="M41.9519 2.94243C41.151 2.53509 40.1223 2.5698 39.0038 3.13593L8.17037 18.8494C5.63659 20.1404 3.57921 23.6892 3.57921 26.7684V64.2916C3.57921 66.0107 4.21438 67.2191 5.21547 67.7369L2.13625 66.1696C1.13517 65.6587 0.5 64.4435 0.5 62.7244V25.2012C0.5 22.1151 2.55742 18.5732 5.0912 17.2821L35.9246 1.5687C37.05 0.995669 38.0787 0.967864 38.8726 1.3752L41.9519 2.94243Z"
d="M68.8637 14.8332C68.0629 14.4259 67.0342 14.4602 65.9157 15.0264L35.0823 30.7401C32.5485 32.0312 30.4911 35.5797 30.4911 38.6589V76.1824C30.4911 77.9015 31.1263 79.1098 32.1274 79.6276L29.0482 78.0604C28.0471 77.5495 27.4119 76.3342 27.4119 74.6151V37.0916C27.4119 34.0055 29.4693 30.4639 32.0031 29.1729L62.8365 13.4591C63.9619 12.8861 64.9906 12.8586 65.7845 13.266L68.8637 14.8332Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.secondary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.4"
@ -13,59 +56,25 @@ export const ExportHorizontalStackIllustration = ({ className }: TIllustrationAs
strokeLinejoin="round"
/>
<path
d="M41.9519 2.94356C42.9529 3.45446 43.5881 4.66973 43.5881 6.38884V43.912C43.5881 46.9981 41.5307 50.54 38.9969 51.8311L8.16351 67.5448C7.03816 68.1179 6.00944 68.1453 5.21548 67.738C4.21439 67.2271 3.57922 66.0118 3.57922 64.2927V26.7696C3.57922 23.6835 5.6366 20.1415 8.17038 18.8505L39.0038 3.13706C40.1292 2.56403 41.1579 2.53622 41.9519 2.94356Z"
d="M68.8637 14.8304C69.8648 15.3413 70.5 16.5563 70.5 18.2754V55.7989C70.5 58.885 68.4426 62.4269 65.9088 63.7179L35.0754 79.4313C33.95 80.0044 32.9213 80.0322 32.1273 79.6248C31.1263 79.1139 30.4911 77.8987 30.4911 76.1796V38.6561C30.4911 35.57 32.5485 32.0284 35.0823 30.7373L65.9157 15.0236C67.041 14.4505 68.0698 14.4231 68.8637 14.8304Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.primary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.4"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
<g opacity="0.6">
<path
d="M55.4078 8.88579C54.6069 8.47845 53.5782 8.51316 52.4597 9.07929L21.6263 24.7927C19.0926 26.0838 17.0351 29.6326 17.0351 32.7118V70.2353C17.0351 71.9544 17.6703 73.1624 18.6714 73.6802L15.5922 72.113C14.5911 71.6021 13.9559 70.3872 13.9559 68.6681V31.1446C13.9559 28.0585 16.0134 24.5165 18.5471 23.2255L49.3805 7.51206C50.5059 6.93903 51.5346 6.91122 52.3286 7.31856L55.4078 8.88579Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.secondary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.4"
strokeLinecap="round"
strokeLinejoin="round"
fillRule="evenodd"
clipRule="evenodd"
d="M43.5881 41.1008C43.5881 40.4104 44.0438 39.6233 44.6099 39.3333L58.9289 32.0359C59.495 31.7459 59.9507 32.0702 59.9507 32.7607C59.9507 33.4511 59.495 34.2382 58.9289 34.5281L44.6099 41.8259C44.0438 42.1159 43.5881 41.7912 43.5881 41.1008Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
/>
<path
d="M55.4079 8.88692C56.4089 9.39782 57.0441 10.6131 57.0441 12.3322V49.8557C57.0441 52.9418 54.9867 56.4834 52.4529 57.7744L21.6195 73.4882C20.4941 74.0612 19.4654 74.0887 18.6715 73.6814C17.6704 73.1704 17.0352 71.9555 17.0352 70.2364V32.7129C17.0352 29.6268 19.0926 26.0849 21.6264 24.7938L52.4598 9.08042C53.5852 8.50739 54.6139 8.47958 55.4079 8.88692Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.primary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.4"
strokeLinecap="round"
strokeLinejoin="round"
fillRule="evenodd"
clipRule="evenodd"
d="M58.639 46.7626C58.2385 47.453 57.5895 47.7774 57.1891 47.5013L52.7981 44.3944V58.81C52.7981 59.5004 52.3356 60.2875 51.7764 60.5775C51.2171 60.8674 50.7546 60.5362 50.7546 59.8527V45.43L46.3636 53.0107C45.9631 53.7011 45.3142 54.0323 44.9137 53.7493C44.5202 53.4662 44.5202 52.6793 44.9137 51.9889L51.0514 41.3982C51.0514 41.3982 51.0928 41.336 51.1135 41.3014C51.1205 41.2945 51.1274 41.2878 51.1274 41.2809C51.2033 41.1704 51.2862 41.0598 51.3759 40.9701C51.4104 40.9355 51.445 40.9009 51.4795 40.8733C51.5071 40.8457 51.5416 40.8182 51.5761 40.7975C51.6107 40.7698 51.6521 40.7421 51.6866 40.7145C51.7211 40.6938 51.7487 40.68 51.7833 40.6592C51.8178 40.6385 51.8523 40.6248 51.8868 40.611C52.018 40.5558 52.1492 40.5419 52.2666 40.5558C52.2942 40.5558 52.3218 40.5628 52.3494 40.5767C52.3494 40.5767 52.4046 40.5972 52.4322 40.611C52.4737 40.6318 52.5082 40.6525 52.5427 40.6801L58.6459 44.9952C59.0394 45.2782 59.0394 46.0652 58.6459 46.7556L58.639 46.7626Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
/>
</g>
<path
d="M68.8637 14.8332C68.0629 14.4259 67.0342 14.4602 65.9157 15.0264L35.0823 30.7401C32.5485 32.0312 30.4911 35.5797 30.4911 38.6589V76.1824C30.4911 77.9015 31.1263 79.1098 32.1274 79.6276L29.0482 78.0604C28.0471 77.5495 27.4119 76.3342 27.4119 74.6151V37.0916C27.4119 34.0055 29.4693 30.4639 32.0031 29.1729L62.8365 13.4591C63.9619 12.8861 64.9906 12.8586 65.7845 13.266L68.8637 14.8332Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.secondary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.4"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M68.8637 14.8304C69.8648 15.3413 70.5 16.5563 70.5 18.2754V55.7989C70.5 58.885 68.4426 62.4269 65.9088 63.7179L35.0754 79.4313C33.95 80.0044 32.9213 80.0322 32.1273 79.6248C31.1263 79.1139 30.4911 77.8987 30.4911 76.1796V38.6561C30.4911 35.57 32.5485 32.0284 35.0823 30.7373L65.9157 15.0236C67.041 14.4505 68.0698 14.4231 68.8637 14.8304Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.fill.primary}
stroke={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
strokeWidth="0.4"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M43.5881 41.1008C43.5881 40.4104 44.0438 39.6233 44.6099 39.3333L58.9289 32.0359C59.495 31.7459 59.9507 32.0702 59.9507 32.7607C59.9507 33.4511 59.495 34.2382 58.9289 34.5281L44.6099 41.8259C44.0438 42.1159 43.5881 41.7912 43.5881 41.1008Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M58.639 46.7626C58.2385 47.453 57.5895 47.7774 57.1891 47.5013L52.7981 44.3944V58.81C52.7981 59.5004 52.3356 60.2875 51.7764 60.5775C51.2171 60.8674 50.7546 60.5362 50.7546 59.8527V45.43L46.3636 53.0107C45.9631 53.7011 45.3142 54.0323 44.9137 53.7493C44.5202 53.4662 44.5202 52.6793 44.9137 51.9889L51.0514 41.3982C51.0514 41.3982 51.0928 41.336 51.1135 41.3014C51.1205 41.2945 51.1274 41.2878 51.1274 41.2809C51.2033 41.1704 51.2862 41.0598 51.3759 40.9701C51.4104 40.9355 51.445 40.9009 51.4795 40.8733C51.5071 40.8457 51.5416 40.8182 51.5761 40.7975C51.6107 40.7698 51.6521 40.7421 51.6866 40.7145C51.7211 40.6938 51.7487 40.68 51.7833 40.6592C51.8178 40.6385 51.8523 40.6248 51.8868 40.611C52.018 40.5558 52.1492 40.5419 52.2666 40.5558C52.2942 40.5558 52.3218 40.5628 52.3494 40.5767C52.3494 40.5767 52.4046 40.5972 52.4322 40.611C52.4737 40.6318 52.5082 40.6525 52.5427 40.6801L58.6459 44.9952C59.0394 45.2782 59.0394 46.0652 58.6459 46.7556L58.639 46.7626Z"
fill={ILLUSTRATION_COLOR_TOKEN_MAP.stroke.secondary}
/>
</svg>
);
</svg>
);
}

Some files were not shown because too many files have changed in this diff Show more