Use nested css

This commit is contained in:
Arnaud 2024-10-30 19:20:43 +01:00
parent ad844be552
commit d004ba56fd
No known key found for this signature in database
GPG Key ID: 69D6CE281FCAE663
56 changed files with 1348 additions and 1745 deletions

110
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "@codex-storage/marketplace-ui-components", "name": "@codex-storage/marketplace-ui-components",
"version": "0.0.27", "version": "0.0.28",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@codex-storage/marketplace-ui-components", "name": "@codex-storage/marketplace-ui-components",
"version": "0.0.27", "version": "0.0.28",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"lucide-react": "^0.453.0" "lucide-react": "^0.453.0"
@ -41,6 +41,7 @@
}, },
"peerDependencies": { "peerDependencies": {
"@codex-storage/sdk-js": ">=0.0.12", "@codex-storage/sdk-js": ">=0.0.12",
"postcss-nesting": "^13.0.1",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1" "react-dom": "^18.3.1"
} }
@ -628,6 +629,50 @@
"node": ">=20" "node": ">=20"
} }
}, },
"node_modules/@csstools/selector-resolve-nested": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.0.0.tgz",
"integrity": "sha512-ZoK24Yku6VJU1gS79a5PFmC8yn3wIapiKmPgun0hZgEI5AOqgH2kiPRsPz1qkGv4HL+wuDLH83yQyk6inMYrJQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"peer": true,
"engines": {
"node": ">=18"
},
"peerDependencies": {
"postcss-selector-parser": "^7.0.0"
}
},
"node_modules/@csstools/selector-specificity": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz",
"integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"peer": true,
"engines": {
"node": ">=18"
},
"peerDependencies": {
"postcss-selector-parser": "^7.0.0"
}
},
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
"version": "0.23.1", "version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
@ -3770,6 +3815,18 @@
"integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
"dev": true "dev": true
}, },
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"peer": true,
"bin": {
"cssesc": "bin/cssesc"
},
"engines": {
"node": ">=4"
}
},
"node_modules/csstype": { "node_modules/csstype": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
@ -5558,7 +5615,6 @@
"version": "3.3.7", "version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -5824,8 +5880,7 @@
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
"dev": true
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "2.3.1", "version": "2.3.1",
@ -5939,7 +5994,6 @@
"version": "8.4.47", "version": "8.4.47",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -5964,6 +6018,46 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/postcss-nesting": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.1.tgz",
"integrity": "sha512-VbqqHkOBOt4Uu3G8Dm8n6lU5+9cJFxiuty9+4rcoyRPO9zZS1JIs6td49VIoix3qYqELHlJIn46Oih9SAKo+yQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"peer": true,
"dependencies": {
"@csstools/selector-resolve-nested": "^3.0.0",
"@csstools/selector-specificity": "^5.0.0",
"postcss-selector-parser": "^7.0.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"postcss": "^8.4"
}
},
"node_modules/postcss-selector-parser": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz",
"integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==",
"peer": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
},
"engines": {
"node": ">=4"
}
},
"node_modules/prelude-ls": { "node_modules/prelude-ls": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@ -6673,7 +6767,6 @@
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -7134,8 +7227,7 @@
"node_modules/util-deprecate": { "node_modules/util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
"dev": true
}, },
"node_modules/utils-merge": { "node_modules/utils-merge": {
"version": "1.0.1", "version": "1.0.1",

View File

@ -5,7 +5,7 @@
"type": "git", "type": "git",
"url": "https://github.com/codex-storage/codex-marketplace-ui-components" "url": "https://github.com/codex-storage/codex-marketplace-ui-components"
}, },
"version": "0.0.27", "version": "0.0.28",
"type": "module", "type": "module",
"scripts": { "scripts": {
"prepack": "npm run build", "prepack": "npm run build",
@ -37,7 +37,8 @@
"peerDependencies": { "peerDependencies": {
"@codex-storage/sdk-js": ">=0.0.12", "@codex-storage/sdk-js": ">=0.0.12",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1" "react-dom": "^18.3.1",
"postcss-nesting": "^13.0.1"
}, },
"devDependencies": { "devDependencies": {
"@chromatic-com/storybook": "^2.0.2", "@chromatic-com/storybook": "^2.0.2",

View File

@ -1,12 +1,5 @@
import "./alert.css"; import "./alert.css";
import { CSSProperties, ReactNode } from "react"; import { ReactNode } from "react";
interface CustomStyleCSS extends CSSProperties {
"--codex-border-radius"?: string;
"--codex-color-primary"?: string;
"--codex-color-warning"?: string;
"--codex-font-family"?: string;
}
type Props = { type Props = {
variant: "success" | "warning" | "toast"; variant: "success" | "warning" | "toast";
@ -20,21 +13,11 @@ type Props = {
*/ */
className?: string; className?: string;
/**
* Apply custom css variables.
* --codex-border-radius
* --codex-color-primary: string;
* --codex-color-warning
* --codex-font-family
*/
style?: CustomStyleCSS;
Icon?: ReactNode; Icon?: ReactNode;
}; };
export function Alert({ export function Alert({
variant, variant,
style,
title, title,
Icon, Icon,
children, children,
@ -42,20 +25,16 @@ export function Alert({
...rest ...rest
}: Props) { }: Props) {
return ( return (
<div <div className={`alert alert--${variant} ${className}`} {...rest}>
className={`alert alert--${variant} ${className}`}
style={style}
{...rest}
>
{Icon && ( {Icon && (
<span className="alert-circleIcon"> <span>
<span className="alert-icon">{Icon}</span> <span>{Icon}</span>
</span> </span>
)} )}
<div className="alert-body"> <div>
<b className="alert-title">{title}</b> <b>{title}</b>
<div className="alert-message">{children}</div> <div>{children}</div>
</div> </div>
</div> </div>
); );

View File

@ -9,34 +9,34 @@
word-break: break-word; word-break: break-word;
display: flex; display: flex;
gap: 1rem; gap: 1rem;
}
.alert-icon { b {
display: flex; text-transform: capitalize;
width: 1rem; margin-bottom: 0.25rem;
color: rgb(var(--codex-alert-color)); display: inline-block;
} }
.alert-circleIcon { span {
background: rgba(var(--codex-alert-color), 0.2); background: rgba(var(--codex-alert-color), 0.2);
border-radius: 75px; border-radius: 75px;
height: 1.75rem; height: 1.75rem;
min-width: 1.75rem; min-width: 1.75rem;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
}
.alert-title { span {
text-transform: capitalize; display: flex;
margin-bottom: 0.25rem; width: 1rem;
display: inline-block; color: rgb(var(--codex-alert-color));
} }
}
.alert--success { &.alert--success {
--codex-alert-color: var(--codex-color-success); --codex-alert-color: var(--codex-color-success);
} }
.alert--warning { &.alert--warning {
--codex-alert-color: var(--codex-color-warning); --codex-alert-color: var(--codex-color-warning);
}
} }

View File

@ -10,11 +10,11 @@
backdrop-filter: blur(2px); backdrop-filter: blur(2px);
display: block; display: block;
z-index: -1; z-index: -1;
}
.backdrop[aria-expanded] { &[aria-expanded] {
z-index: 10; z-index: 10;
opacity: 1; opacity: 1;
}
} }
.document-noOverflow { .document-noOverflow {

View File

@ -1,20 +1,7 @@
import { ComponentType, CSSProperties } from "react"; import { ComponentType } from "react";
import "./button.css"; import "./button.css";
import { attributes } from "../utils/attributes"; import { attributes } from "../utils/attributes";
interface CustomStyleCSS extends CSSProperties {
"--codex-color-primary"?: string;
"--codex-border-radius"?: string;
"--codex-border-color"?: string;
"--codex-font-family"?: string;
"--codex-color-on-primary"?: string;
"--codex-color-disabled"?: string;
"--codex-color-outline"?: string;
"--codex-button-loader"?: string;
"--codex-button-background-busy"?: string;
"--codex-button-color-box-shadow"?: string;
}
type Props = { type Props = {
/** /**
* Button style variant. Default is primary. * Button style variant. Default is primary.
@ -50,21 +37,6 @@ type Props = {
* Apply custom classname. * Apply custom classname.
*/ */
className?: string; className?: string;
/**
* Apply custom css variables.
* --codex-color-primary
* --codex-border-radius
* --codex-border-color
* --codex-font-family
* --codex-color-on-primary: The colors when the button is primary
* --codex-color-disabled: The disabled background color
* --codex-color-outline: The color when the button is outline
* --codex-button-loader: The url svg image for the spinner
* --codex-button-background-busy: The background color image when the button is busy
* --codex-button-color-box-shadow: The shadow color on focus
*/
style?: CustomStyleCSS;
}; };
export function Button({ export function Button({
@ -75,7 +47,6 @@ export function Button({
onMouseLeave, onMouseLeave,
fetching = false, fetching = false,
disabled = false, disabled = false,
style,
variant = "primary", variant = "primary",
onClick, onClick,
}: Props) { }: Props) {
@ -84,7 +55,6 @@ export function Button({
onClick={onClick} onClick={onClick}
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
style={style}
className={`button ${className} button--${variant}`} className={`button ${className} button--${variant}`}
{...attributes({ {...attributes({
disabled: disabled || fetching, disabled: disabled || fetching,
@ -93,11 +63,11 @@ export function Button({
})} })}
> >
{Icon && ( {Icon && (
<div className="button-icon"> <div>
<Icon /> <Icon />
</div> </div>
)} )}
<span className="button-label">{label}</span> <span>{label}</span>
</button> </button>
); );
} }

View File

@ -14,84 +14,84 @@
font-family: var(--codex-font-family); font-family: var(--codex-font-family);
border: 1px solid transparent; border: 1px solid transparent;
place-content: center; place-content: center;
}
&[aria-busy] {
.button--primary { cursor: wait;
background-color: var(--codex-color-primary); }
color: var(--codex-color-on-primary);
} &[aria-busy]::after {
content: " ";
.button:disabled { display: block;
cursor: not-allowed; background-image: var(
} --codex-button-loader,
url('data:image/svg+xml,<svg version="1.1" id="loader-1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 50 50" style="enable-background: new 0 0 50 50" xml:space="preserve"><path fill="%23FFF" d="M43.935,25.145c0-10.318-8.364-18.683-18.683-18.683c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615c8.072,0,14.615,6.543,14.615,14.615H43.935z"><animateTransform attributeType="xml" attributeName="transform" type="rotate" from="0 25 25" to="360 25 25" dur="0.6s" repeatCount="indefinite"></animateTransform></path></svg>')
.button--primary:disabled { );
background-color: var(--codex-color-disabled); position: absolute;
} background-color: var(
--codex-button-background-busy,
.button--outline:disabled { var(--codex-background-backdrop)
color: var(--codex-color-disabled); );
}
/**
.button-label { * Set full size and add border.
display: flex; */
align-items: center; width: calc(100% + 2px);
gap: 0.75rem; height: calc(100% + 2px);
}
background-repeat: no-repeat;
.button--outline { background-position: center;
color: var(--codex-color-outline, var(--codex-color-contrast)); background-size: 28px;
border-color: var(--codex-border-color); left: -1px;
border-width: 1px; right: 0;
border-style: solid; border-radius: var(--codex-border-radius);
background-color: transparent; }
}
&.button--primary:not(:disabled):hover {
.button[aria-busy] { cursor: pointer;
cursor: wait; box-shadow: 0 0 0 3px
} var(--codex-button-color-box-shadow, var(--codex-color-primary-variant));
}
.button-icon {
width: 16px; &.button--outline:not(:disabled):hover {
height: 16px; cursor: pointer;
display: flex; box-shadow: 0 0 0 2px var(--codex-border-color);
place-items: center; }
}
&.button--primary {
.button[aria-busy]::after { background-color: var(--codex-color-primary);
content: " "; color: var(--codex-color-on-primary);
display: block; }
background-image: var(
--codex-button-loader, &:disabled {
url('data:image/svg+xml,<svg version="1.1" id="loader-1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 50 50" style="enable-background: new 0 0 50 50" xml:space="preserve"><path fill="%23FFF" d="M43.935,25.145c0-10.318-8.364-18.683-18.683-18.683c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615c8.072,0,14.615,6.543,14.615,14.615H43.935z"><animateTransform attributeType="xml" attributeName="transform" type="rotate" from="0 25 25" to="360 25 25" dur="0.6s" repeatCount="indefinite"></animateTransform></path></svg>') cursor: not-allowed;
); }
position: absolute;
background-color: var( &.button--primary:disabled {
--codex-button-background-busy, background-color: var(--codex-color-disabled);
var(--codex-background-backdrop) }
);
&.button--outline:disabled {
/** color: var(--codex-color-disabled);
* Set full size and add border. }
*/
width: calc(100% + 2px); &.button--outline {
height: calc(100% + 2px); color: var(--codex-color-outline, var(--codex-color-contrast));
border-color: var(--codex-border-color);
background-repeat: no-repeat; border-width: 1px;
background-position: center; border-style: solid;
background-size: 28px; background-color: transparent;
left: -1px; }
right: 0;
border-radius: var(--codex-border-radius); span {
} display: flex;
align-items: center;
.button--primary:not(:disabled):hover { gap: 0.75rem;
cursor: pointer; }
box-shadow: 0 0 0 3px
var(--codex-button-color-box-shadow, var(--codex-color-primary-variant)); div {
} width: 16px;
height: 16px;
.button--outline:not(:disabled):hover { display: flex;
cursor: pointer; place-items: center;
box-shadow: 0 0 0 2px var(--codex-border-color); }
} }

View File

@ -1,18 +1,7 @@
import { import { AnimationEventHandler, ComponentType, useState } from "react";
AnimationEventHandler,
ComponentType,
CSSProperties,
useState,
} from "react";
import "./buttonIcon.css"; import "./buttonIcon.css";
import { attributes } from "../utils/attributes"; import { attributes } from "../utils/attributes";
interface CustomStyleCSS extends CSSProperties {
"--codex-button-icon-background"?: string;
"--codex-border-color"?: string;
"--codex-color-disabled"?: string;
}
type Props = { type Props = {
Icon: ComponentType<{ Icon: ComponentType<{
className?: string; className?: string;
@ -29,14 +18,6 @@ type Props = {
disabled?: boolean; disabled?: boolean;
/**
* Apply custom css variables.
* --codex-button-icon-background
* --codex-border-color
* --codex-color-disabled
*/
style?: CustomStyleCSS;
/** /**
* Apply custom classname. * Apply custom classname.
*/ */
@ -51,7 +32,6 @@ type Props = {
export function ButtonIcon({ export function ButtonIcon({
Icon, Icon,
onClick, onClick,
style,
onMouseEnter, onMouseEnter,
onMouseLeave, onMouseLeave,
className = "", className = "",
@ -74,7 +54,6 @@ export function ButtonIcon({
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
onClick={onInternalClick} onClick={onInternalClick}
style={style}
{...attributes({ disabled: disabled, "aria-disabled": disabled })} {...attributes({ disabled: disabled, "aria-disabled": disabled })}
> >
<Icon className={animationClassName} onAnimationEnd={onAnimationEnd} /> <Icon className={animationClassName} onAnimationEnd={onAnimationEnd} />

View File

@ -10,41 +10,52 @@
cursor: pointer; cursor: pointer;
transition: box-shadow 0.35s; transition: box-shadow 0.35s;
border: none; border: none;
}
.buttonIcon--big { &.buttonIcon--big {
width: 4rem; width: 4rem;
height: 4rem; height: 4rem;
} }
.buttonIcon--small { &.buttonIcon--small {
width: 2rem; width: 2rem;
height: 2rem; height: 2rem;
} }
.buttonIcon svg { &:not(:disabled):hover {
mix-blend-mode: difference; cursor: pointer;
} box-shadow: 0 0 0 2px var(--codex-border-color);
}
.buttonIcon:not(:disabled):hover { &:disabled {
cursor: pointer; color: var(--codex-color-disabled);
box-shadow: 0 0 0 2px var(--codex-border-color); cursor: not-allowed;
} }
.buttonIcon:disabled { &.buttonIcon--buzz {
color: var(--codex-color-disabled); -webkit-animation-name: buzz;
cursor: not-allowed; animation-name: buzz;
} -webkit-animation-duration: 0.45s;
animation-duration: 0.45s;
-webkit-animation-timing-function: linear;
animation-timing-function: linear;
-webkit-animation-iteration-count: 5;
animation-iteration-count: 5;
}
.buttonIcon--buzz { &.buttonIcon--bounce {
-webkit-animation-name: buzz; -webkit-animation-name: bounce;
animation-name: buzz; animation-name: bounce;
-webkit-animation-duration: 0.45s; -webkit-animation-duration: 0.6s;
animation-duration: 0.45s; animation-duration: 0.6s;
-webkit-animation-timing-function: linear; -webkit-animation-timing-function: linear;
animation-timing-function: linear; animation-timing-function: linear;
-webkit-animation-iteration-count: 5; -webkit-animation-iteration-count: 1;
animation-iteration-count: 5; animation-iteration-count: 1;
}
svg {
mix-blend-mode: difference;
}
} }
@keyframes buzz { @keyframes buzz {
@ -74,17 +85,6 @@
} }
} }
.buttonIcon--bounce {
-webkit-animation-name: bounce;
animation-name: bounce;
-webkit-animation-duration: 0.6s;
animation-duration: 0.6s;
-webkit-animation-timing-function: linear;
animation-timing-function: linear;
-webkit-animation-iteration-count: 1;
animation-iteration-count: 1;
}
@keyframes bounce { @keyframes bounce {
0% { 0% {
-webkit-transform: translateY(0) scale(1); -webkit-transform: translateY(0) scale(1);

View File

@ -1,33 +1,19 @@
import { CSSProperties, ReactNode } from "react"; import { ReactNode } from "react";
import "./card.css"; import "./card.css";
interface CustomStyleCSS extends CSSProperties {
"--codex-border-radius"?: string;
"--codex-border-color"?: string;
"--codex-font-family"?: string;
}
type Props = { type Props = {
children: ReactNode; children: ReactNode;
className?: string; className?: string;
title: string; title: string;
/**
* Apply custom css variables.
* --codex-border-radius
* --codex-border-color
* --codex-font-family
*/
style?: CustomStyleCSS;
}; };
export function Card({ children, style, className = "", title }: Props) { export function Card({ children, className = "", title }: Props) {
return ( return (
<div className={`card ${className}`} style={style}> <div className={`card ${className}`}>
<div className="card-header">{title}</div> <header>{title}</header>
<div className="card-body">{children}</div> <div>{children}</div>
</div> </div>
); );
} }

View File

@ -3,15 +3,15 @@
border: 1px solid var(--codex-border-color); border: 1px solid var(--codex-border-color);
font-family: var(--codex-font-family); font-family: var(--codex-font-family);
background-color: var(--codex-background-secondary); background-color: var(--codex-background-secondary);
}
.card-header { header {
border-bottom: 1px solid var(--codex-border-color); border-bottom: 1px solid var(--codex-border-color);
padding: 1rem 1.5rem; padding: 1rem 1.5rem;
font-size: 1.15rem; font-size: 1.15rem;
font-weight: bold; font-weight: bold;
} }
.card-body { div {
padding: 1.5rem; padding: 1.5rem;
}
} }

View File

@ -8,15 +8,9 @@ import {
import "./dropdown.css"; import "./dropdown.css";
import { attributes } from "../utils/attributes"; import { attributes } from "../utils/attributes";
import { Backdrop } from "../Backdrop/Backdrop"; import { Backdrop } from "../Backdrop/Backdrop";
import { Input, InputCustomStyleCSS } from "../Input/Input"; import { Input } from "../Input/Input";
import { classnames } from "../utils/classnames"; import { classnames } from "../utils/classnames";
interface CustomStyleCSS extends InputCustomStyleCSS {
"--codex-dropdown-panel-background"?: string;
"--codex-dropdown-border"?: string;
"--codex-dropdown-option-background-hover"?: string;
}
export type DropdownOption = { export type DropdownOption = {
/** /**
* Dropdown option icon displayed on the left * Dropdown option icon displayed on the left
@ -70,14 +64,6 @@ type Props = {
onMouseLeave?: () => void; onMouseLeave?: () => void;
/**
* Apply custom css variables.
* --codex-dropdown-panel-background
* --codex-dropdown-border
* --codex-dropdown-option-background-hover
*/
style?: CustomStyleCSS;
label: string; label: string;
id: string; id: string;
@ -85,7 +71,6 @@ type Props = {
export function Dropdown({ export function Dropdown({
placeholder, placeholder,
style,
options, options,
label, label,
id, id,
@ -134,12 +119,10 @@ export function Dropdown({
const attr = attributes({ "aria-expanded": focused }); const attr = attributes({ "aria-expanded": focused });
return ( return (
<> <div className={"dropdown " + className}>
<label className="dropdown-label" htmlFor={id}> <label htmlFor={id}>{label}</label>
{label}
</label>
<div className={`dropdown ${className}`} style={style}> <div>
<Backdrop onClose={onClose} open={focused} /> <Backdrop onClose={onClose} open={focused} />
<Input <Input
@ -157,28 +140,20 @@ export function Dropdown({
id={id} id={id}
/> />
<div className="dropdown-panel" {...attr}> <ul className="dropdown-panel" {...attr}>
{filtered.length ? ( {filtered.length ? (
filtered.map((o) => ( filtered.map((o) => (
<div <li onClick={() => onSelect(o)} key={o.title}>
className="dropdown-option"
onClick={() => onSelect(o)}
key={o.title}
>
{o.Icon && <o.Icon />} {o.Icon && <o.Icon />}
<div> <span>{o.title}</span>
<span className="dropdown-title">{o.title}</span> {o.subtitle && <span>{o.subtitle}</span>}
{o.subtitle && ( </li>
<span className="dropdown-subtitle">{o.subtitle}</span>
)}
</div>
</div>
)) ))
) : ( ) : (
<p className="dropdown-noResults">No results found</p> <p>No results found</p>
)} )}
</div> </ul>
</div> </div>
</> </div>
); );
} }

View File

@ -1,85 +1,83 @@
.dropdown { .dropdown {
position: relative; label {
} margin-bottom: 0.5rem;
font-weight: 500;
display: block;
color: var(--codex-color);
}
.dropdown-panel { > div {
position: absolute; position: relative;
padding: 0.5rem; }
background-color: var(
--codex-dropdown-panel-background,
var(--codex-background-secondary)
);
border: var(--codex-dropdown-border, 1px solid var(--codex-border-color));
border-radius: var(--codex-border-radius);
left: 0;
right: 0;
opacity: 0;
transform: translateY(-3rem);
transition:
transform 0.35s,
opacity 0.15s,
z-index 0.35s;
max-height: 20rem;
overflow-y: auto;
z-index: -1;
}
.dropdown-label { ul {
margin-bottom: 0.5rem; position: absolute;
font-weight: 500; padding: 0.5rem;
display: block; background-color: var(
color: var(--codex-color); --codex-dropdown-panel-background,
} var(--codex-background-secondary)
);
border: var(--codex-dropdown-border, 1px solid var(--codex-border-color));
border-radius: var(--codex-border-radius);
left: 0;
right: 0;
opacity: 0;
transform: translateY(-3rem);
transition:
transform 0.35s,
opacity 0.15s,
z-index 0.35s;
max-height: 20rem;
overflow-y: auto;
z-index: -1;
.dropdown-panel[aria-expanded] { &[aria-expanded] {
transform: translateY(0.5rem); transform: translateY(0rem);
opacity: 1; opacity: 1;
z-index: 10; z-index: 10;
} }
.dropdown-input { li {
position: relative; padding: 0.5rem;
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M8.35355 4.06066C8.15829 3.8654 7.84171 3.8654 7.64645 4.06066L5.35355 6.35355C5.15829 6.54882 4.84171 6.54882 4.64645 6.35355C4.45118 6.15829 4.45118 5.84171 4.64645 5.64645L6.93934 3.35356C7.52513 2.76777 8.47487 2.76777 9.06066 3.35355L11.3536 5.64645C11.5488 5.84171 11.5488 6.15829 11.3536 6.35355C11.1583 6.54882 10.8417 6.54882 10.6464 6.35355L8.35355 4.06066Z' fill='%236b7280'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M8.35355 11.9393C8.15829 12.1346 7.84171 12.1346 7.64645 11.9393L5.35355 9.64645C5.15829 9.45119 4.84171 9.45119 4.64645 9.64645C4.45118 9.84171 4.45118 10.1583 4.64645 10.3536L6.93934 12.6464C7.52513 13.2322 8.47487 13.2322 9.06066 12.6464L11.3536 10.3536C11.5488 10.1583 11.5488 9.84171 11.3536 9.64645C11.1583 9.45119 10.8417 9.45119 10.6464 9.64645L8.35355 11.9393Z' fill='%236b7280'/%3E%3C/svg%3E%0A"); border-radius: var(--codex-border-radius);
background-position: right 0.5rem center; transition: background-color 0.35s;
background-repeat: no-repeat; cursor: pointer;
background-size: 1.25em 1.25em; display: flex;
} align-items: center;
gap: 0.75rem;
.dropdown-input:focus { &:hover {
z-index: 11; background-color: var(
} --codex-dropdown-option-background-hover,
var(--codex-background-light)
);
}
.dropdown-option { span {
padding: 0.5rem; display: block;
border-radius: var(--codex-border-radius); word-break: break-all;
transition: background-color 0.35s; }
cursor: pointer;
display: flex;
align-items: center;
gap: 0.75rem;
}
.dropdown-option:hover { span + span {
background-color: var( mix-blend-mode: difference;
--codex-dropdown-option-background-hover, font-size: 0.75rem;
var(--codex-background-light) }
); }
} }
.dropdown-noResults { .input {
padding: 0.75rem 0.25rem; position: relative;
} background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M8.35355 4.06066C8.15829 3.8654 7.84171 3.8654 7.64645 4.06066L5.35355 6.35355C5.15829 6.54882 4.84171 6.54882 4.64645 6.35355C4.45118 6.15829 4.45118 5.84171 4.64645 5.64645L6.93934 3.35356C7.52513 2.76777 8.47487 2.76777 9.06066 3.35355L11.3536 5.64645C11.5488 5.84171 11.5488 6.15829 11.3536 6.35355C11.1583 6.54882 10.8417 6.54882 10.6464 6.35355L8.35355 4.06066Z' fill='%236b7280'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M8.35355 11.9393C8.15829 12.1346 7.84171 12.1346 7.64645 11.9393L5.35355 9.64645C5.15829 9.45119 4.84171 9.45119 4.64645 9.64645C4.45118 9.84171 4.45118 10.1583 4.64645 10.3536L6.93934 12.6464C7.52513 13.2322 8.47487 13.2322 9.06066 12.6464L11.3536 10.3536C11.5488 10.1583 11.5488 9.84171 11.3536 9.64645C11.1583 9.45119 10.8417 9.45119 10.6464 9.64645L8.35355 11.9393Z' fill='%236b7280'/%3E%3C/svg%3E%0A");
background-position: right 0.5rem center;
background-repeat: no-repeat;
background-size: 1.25em 1.25em;
.dropdown-title { &:focus {
display: block; z-index: 11;
} }
}
.dropdown-subtitle { p {
mix-blend-mode: difference; padding: 0.75rem 0.25rem;
font-size: 0.75rem; }
}
.dropdown-title,
.dropdown-subtitle {
word-break: break-all;
} }

View File

@ -1,12 +1,5 @@
import "./failure.css"; import "./failure.css";
import { Button } from "../Button/Button"; import { Button } from "../Button/Button";
import { CSSProperties } from "react";
interface CustomStyleCSS extends CSSProperties {
"--codex-code-font-size"?: string;
"--codex-text-contrast"?: string;
"--codex-font-family"?: string;
}
type Props = { type Props = {
/** /**
@ -29,14 +22,6 @@ type Props = {
* The button label * The button label
*/ */
button?: string; button?: string;
/**
* Apply custom css variables.
* --codex-code-font-size
* --codex-text-contrast
* --codex-font-family
*/
style?: CustomStyleCSS;
}; };
export function Failure({ export function Failure({
@ -48,9 +33,9 @@ export function Failure({
}: Props) { }: Props) {
return ( return (
<div className="failure"> <div className="failure">
<h1 className="failure-code">{code}</h1> <h1>{code}</h1>
<h2 className="failure-title">{title}</h2> <h2>{title}</h2>
<div className="failure-message">{message}</div> <div>{message}</div>
{onClick && <Button label={button} onClick={onClick} />} {onClick && <Button label={button} onClick={onClick} />}
</div> </div>
); );

View File

@ -1,28 +1,28 @@
.failure-code {
font-size: var(--codex-code-font-size, 6rem);
line-height: 1;
color: var(--codex-text-contrast);
font-family: var(--codex-font-family);
margin: 0;
}
.failure { .failure {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex-direction: column; flex-direction: column;
flex: 1; flex: 1;
}
.failure-title { h1 {
font-size: 1.875rem; font-size: var(--codex-code-font-size, 6rem);
line-height: 2.25rem; line-height: 1;
font-weight: 600; color: var(--codex-text-contrast);
margin: 0.75rem 0; font-family: var(--codex-font-family);
color: var(--codex-text-contrast); margin: 0;
} }
.failure-message { h2 {
margin-bottom: 0.75rem; font-size: 1.875rem;
mix-blend-mode: difference; line-height: 2.25rem;
font-weight: 600;
margin: 0.75rem 0;
color: var(--codex-text-contrast);
}
div {
margin-bottom: 0.75rem;
mix-blend-mode: difference;
}
} }

View File

@ -1,7 +1,6 @@
import { import {
ChangeEvent, ChangeEvent,
ComponentType, ComponentType,
CSSProperties,
forwardRef, forwardRef,
InputHTMLAttributes, InputHTMLAttributes,
useState, useState,
@ -9,16 +8,6 @@ import {
import { attributes } from "../utils/attributes"; import { attributes } from "../utils/attributes";
import { classnames } from "../utils/classnames"; import { classnames } from "../utils/classnames";
import "./input.css"; import "./input.css";
import { SimpleText } from "../SimpleText/SimpleText";
export interface InputCustomStyleCSS extends CSSProperties {
"--codex-input-background"?: string;
"--codex-color"?: string;
"--codex-border-radius"?: string;
"--codex-input-border"?: string;
"--codex-color-primary"?: string;
"--codex-input-background-disabled"?: string;
}
type Props = { type Props = {
id: string; id: string;
@ -80,54 +69,39 @@ export const Input = forwardRef<HTMLInputElement, Props>(
}; };
return ( return (
<> <div
{label && ( className={classnames(
<label className="input-label" htmlFor={id}> ["input"],
{label} ["input--invalid", invalid || isInvalid],
</label> ["input--icon", !!Icon],
[inputClassName || ""]
)} )}
>
{label && <label htmlFor={id}>{label}</label>}
<div <div className={classnames([inputContainerClassName])}>
className={classnames(
["input-icon", !!Icon],
[inputContainerClassName]
)}
>
{Icon && ( {Icon && (
<div className="input-iconElement"> <div>
<Icon /> <Icon />
</div> </div>
)} )}
<input <input
id={id} id={id}
ref={ref} ref={ref}
className={classnames( className={classnames([inputClassName || ""])}
["input"],
["input--invalid", invalid || isInvalid],
["input-icon-input", !!Icon],
[inputClassName || ""]
)}
onChange={onInternalChange} onChange={onInternalChange}
style={style} style={style}
{...attributes({ {...attributes({
disabled, disabled,
"aria-disabled": disabled, "aria-disabled": disabled,
"aria-invalid": invalid || isInvalid,
})} })}
{...rest} {...rest}
/> />
</div> </div>
{helper && ( {helper && <small>{helper}</small>}
<div> </div>
<SimpleText
className="input-helper-text"
variant={invalid || isInvalid ? "error" : "light"}
>
{helper}
</SimpleText>
</div>
)}
</>
); );
} }
); );

View File

@ -1,105 +1,71 @@
.input { .input {
background-color: var(--codex-input-background); input {
color: white; background-color: var(--codex-input-background);
border-radius: var(--codex-border-radius); color: white;
border: 1px solid var(--codex-input-border-color); border-radius: var(--codex-border-radius);
padding: 16px; border: 1px solid var(--codex-input-border-color);
outline: none; padding: 16px;
display: inline-block; outline: none;
box-sizing: border-box; display: inline-block;
font-weight: 500; box-sizing: border-box;
font-size: 20px; font-weight: 500;
height: 64px; font-size: 20px;
} height: 64px;
position: relative;
.input--invalid {
color: var(--codex-input-color-error);
border-color: var(--codex-input-color-error);
}
.input-label {
margin-bottom: 0.75rem;
display: block;
font-family: var(--codex-input-font-family, var(--codex-font-family));
font-size: 14px;
font-weight: 500;
line-height: 20px;
letter-spacing: -0.006em;
text-align: left;
color: var(--codex-input-label-color);
}
.input:not(.input[disabled]):active,
.input:not(.input[disabled]):focus {
box-shadow: 0 0 0 1px var(--codex-border-color);
}
.input-icon {
position: relative;
}
.input-iconElement {
position: absolute;
height: 100%;
padding: 0 0.75rem;
display: flex;
align-items: center;
}
.input-icon-input {
padding-left: 2.5rem;
}
.input[disabled] {
background-color: var(--codex-input-background-disabled);
cursor: not-allowed;
}
.input-floating {
position: relative;
}
.input-full {
width: 100%;
}
.input-spacing {
margin-bottom: 1.5rem;
}
.input-floating-label {
position: absolute;
font-weight: 600;
color: white;
left: 1rem;
top: 0;
bottom: 0;
display: flex;
align-items: center;
transition:
top 0.15s,
font-size 0.15s;
}
.input-floating-input {
padding-top: 1.75rem;
}
.input.input-floating-input:not(:placeholder-shown) ~ .input-floating-label,
.input.input-floating-input:focus ~ .input-floating-label {
top: -25px;
font-size: 0.875rem;
mix-blend-mode: difference;
}
.input-helper-text {
margin-top: 0.25rem;
display: inline-block;
font-size: 0.9rem;
}
/* @media (min-width: 801px) {
.input {
min-width: 20rem;
} }
} */
&.input--invalid input {
color: var(--codex-input-color-error);
border-color: var(--codex-input-color-error);
}
label {
margin-bottom: 0.75rem;
display: block;
font-family: var(--codex-input-font-family, var(--codex-font-family));
font-size: 14px;
font-weight: 500;
line-height: 20px;
letter-spacing: -0.006em;
text-align: left;
color: var(--codex-input-label-color);
}
& input:not(.input[disabled]):active,
& input:not(.input[disabled]):focus {
box-shadow: 0 0 0 1px var(--codex-border-color);
}
& input[disabled] {
background-color: var(--codex-input-background-disabled);
cursor: not-allowed;
}
&.input--icon input {
padding-left: 2.5rem;
}
> div {
position: relative;
div {
position: absolute;
height: 100%;
padding: 0 0.75rem;
display: flex;
align-items: center;
z-index: 1;
}
}
small {
margin-top: 0.25rem;
display: inline-block;
font-size: 0.9rem;
color: var(--codex-input-label-color);
}
&.input--invalid small {
color: var(--codex-color-error-hexa);
}
}

View File

@ -1,13 +1,8 @@
import { ChangeEvent, CSSProperties, LegacyRef, ReactNode } from "react"; import { ChangeEvent, LegacyRef, ReactNode } from "react";
import "./inputGroup.css"; import "./inputGroup.css";
import { Input } from "../Input/Input"; import { Input } from "../Input/Input";
import { Select } from "../Select/Select"; import { Select } from "../Select/Select";
export interface CustomStyleCSS extends CSSProperties {
"--codex-border-radius"?: string;
"--codex-border-color"?: string;
}
type Props = { type Props = {
label: string; label: string;
@ -63,13 +58,6 @@ type Props = {
step?: string; step?: string;
/**
* Apply custom css variables.
* --codex-border-radius
* --codex-border-color
*/
style?: CustomStyleCSS;
name?: string; name?: string;
/** /**
@ -91,7 +79,6 @@ export function InputGroup({
name, name,
helper, helper,
type = "text", type = "text",
style,
group, group,
className = "", className = "",
inputClassName = "", inputClassName = "",
@ -111,17 +98,17 @@ export function InputGroup({
extra, extra,
}: Props) { }: Props) {
return ( return (
<div className={`inputGroup ${className}`} style={style}> <div className={`inputGroup input-group ${className}`}>
<div className="inputGroup-container"> <div>
<div className="inputGroup-element"> <div>
<div className="inputGroup-inputContainer"> <div>
<Input <Input
ref={inputRef} ref={inputRef}
id={id} id={id}
name={name} name={name}
label={label} label={label}
onChange={onChange} onChange={onChange}
inputClassName={"inputGroup-input " + inputClassName} inputClassName={inputClassName}
type={type} type={type}
value={value} value={value}
step={step} step={step}
@ -149,12 +136,10 @@ export function InputGroup({
)} )}
</div> </div>
</div> </div>
<div className="inputGroup-helpers"> <p>
<span> <span>{helper && <small>{helper}</small>}</span>
{helper && <small className="inputGroup-helper">{helper}</small>}
</span>
{extra} {extra}
</div> </p>
</div> </div>
</div> </div>
); );

View File

@ -1,45 +1,48 @@
.inputGroup-element { .input-group {
display: flex; > div {
align-items: flex-end; flex-grow: 1;
}
.inputGroup-container { > div {
flex-grow: 1; display: flex;
} align-items: flex-end;
.inputGroup-helper { > div:first-child {
height: 15px; flex: 1;
display: inline-block;
}
input.inputGroup-input { input {
border-top-right-radius: 0; border-top-right-radius: 0;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
border-right: none; border-right: none;
} }
select.inputGroup-select { select {
border-top-left-radius: 0; border-top-left-radius: 0;
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
min-width: 110px; min-width: 110px;
} }
}
.inputGroup-unit { > div:nth-child(2) {
display: flex; display: flex;
align-items: center; align-items: center;
border: 1px solid var(--codex-border-color); border: 1px solid var(--codex-border-color);
border-top-right-radius: var(--codex-border-radius); border-top-right-radius: var(--codex-border-radius);
border-bottom-right-radius: var(--codex-border-radius); border-bottom-right-radius: var(--codex-border-radius);
background-color: var(--codex-border-color); background-color: var(--codex-border-color);
padding: calc(0.5rem + 0.5px); padding: calc(0.5rem + 0.5px);
} }
}
}
.inputGroup-inputContainer { p {
flex: 1; display: flex;
} justify-content: space-between;
align-items: center;
max-width: 0;
.inputGroup-helpers { small {
display: flex; height: 15px;
justify-content: space-between; display: inline-block;
align-items: center; }
}
} }

View File

@ -53,6 +53,8 @@ type Props = {
disableActionButton?: boolean; disableActionButton?: boolean;
children: ReactNode; children: ReactNode;
className?: string;
}; };
export function Modal({ export function Modal({
@ -60,6 +62,7 @@ export function Modal({
onClose, onClose,
disableActionButton, disableActionButton,
disableCloseButton, disableCloseButton,
className = "",
displayCloseButton = true, displayCloseButton = true,
displayActionButton = false, displayActionButton = false,
labelActionButton = "Action", labelActionButton = "Action",
@ -79,24 +82,21 @@ export function Modal({
}; };
return ( return (
<> <div
className={classnames(
["modal"],
["modal--internalOpen", internalOpen],
["modal--open", open],
["modal--actions", !!onAction],
[className]
)}
>
<Backdrop open={internalOpen} onClose={internalClose} /> <Backdrop open={internalOpen} onClose={internalClose} />
<div <dialog>
className={classnames( <main>{open && children}</main>
["modal"],
["modal--internalOpen", internalOpen],
["modal--open", open]
)}
>
<div className="modal-body">{open && children}</div>
<div <footer>
className={classnames(
["modal-buttons--between", !!onAction],
["modal-buttons--center", !onAction]
)}
>
{displayCloseButton && ( {displayCloseButton && (
<Button <Button
label={labelCloseButton} label={labelCloseButton}
@ -113,8 +113,8 @@ export function Modal({
disabled={disableActionButton} disabled={disableActionButton}
/> />
)} )}
</div> </footer>
</div> </dialog>
</> </div>
); );
} }

View File

@ -1,64 +1,55 @@
.modal { .modal {
transition: dialog {
transform 0.25s, transition:
opacity 0.25s; transform 0.25s,
max-width: 800px; opacity 0.25s;
overflow-y: auto; max-width: 800px;
overflow-x: hidden; overflow-y: auto;
opacity: 0; overflow-x: hidden;
z-index: -1; opacity: 0;
max-height: 100%; z-index: -1;
left: 50%; max-height: 100%;
top: 50%; left: 50%;
transform: translateX(-50%); top: 50%;
position: fixed; transform: translateX(-50%);
display: flex; position: fixed;
flex-direction: column; display: flex;
background-color: var(--codex-background); flex-direction: column;
padding: 1.5rem; background-color: var(--codex-background);
border-radius: var(--codex-border-radius); padding: 1.5rem;
} border-radius: var(--codex-border-radius);
border: none;
.modal--internalOpen {
transform: translate(-50%, -50%);
opacity: 1;
}
.modal--open {
z-index: 10;
}
.modal-buttons--center {
margin-top: 1rem;
display: flex;
justify-content: center;
}
.modal-buttons--between {
margin-top: 1rem;
display: flex;
justify-content: space-between;
}
.modal-body {
flex: 1;
}
.modal-title {
margin-left: auto;
margin-right: auto;
margin-bottom: 1rem;
font-size: 1.25rem;
}
@media (min-width: 801px) {
.modal {
min-width: 500px;
}
}
@media (max-width: 800px) {
.modal {
width: calc(100% - 6rem); width: calc(100% - 6rem);
@media (min-width: 801px) {
& {
min-width: 500px;
}
}
}
&.modal--internalOpen dialog {
transform: translate(-50%, -50%);
opacity: 1;
}
&.modal--open dialog {
z-index: 10;
}
main {
flex: 1;
}
footer {
margin-top: 1rem;
display: flex;
justify-content: center;
}
&.modal--actions footer {
margin-top: 1rem;
display: flex;
justify-content: space-between;
} }
} }

View File

@ -1,23 +0,0 @@
import { classnames } from "../utils/classnames";
import "./networkIndicator.css";
type Props = {
online: boolean;
text: string;
};
export function NetworkIndicator({ online, text }: Props) {
return (
<div className="networkIndicator">
<div
className={classnames(
["networkIndicator-point"],
["networkIndicator-point--online", online],
["networkIndicator-point--offline", !online]
)}
></div>
<span>{text}</span>
</div>
);
}

View File

@ -1,35 +0,0 @@
.networkIndicator {
display: flex;
align-items: center;
gap: 0.75rem;
}
.networkIndicator-point {
width: 12px;
height: 12px;
border-radius: 50%;
animation-duration: 3s;
animation-name: flash;
animation-iteration-count: infinite;
transition: none;
}
.networkIndicator-point--online {
background-color: var(--codex-color-primary);
}
.networkIndicator-point--offline {
background-color: rgb(217, 53, 38);
}
@keyframes flash {
0% {
opacity: 1;
}
40% {
opacity: 0;
}
100% {
opacity: 1;
}
}

View File

@ -1,7 +1,6 @@
import { ReactNode } from "react"; import { ReactNode } from "react";
import { Button } from "../Button/Button"; import { Button } from "../Button/Button";
import "./placeholder.css"; import "./placeholder.css";
import { SimpleText } from "../SimpleText/SimpleText";
type Props = { type Props = {
title: string; title: string;
@ -35,14 +34,10 @@ export function Placeholder({
}: Props) { }: Props) {
return ( return (
<div className={"placeholder " + className}> <div className={"placeholder " + className}>
<div className="placeholder-icon">{Icon}</div> <div>{Icon}</div>
<b className="placeholder-title">{title}</b> <b>{title}</b>
{subtitle && ( {subtitle && <p>subtitle</p>}
<div className="placeholder-subtitle">
<SimpleText variant="light">{subtitle}</SimpleText>
</div>
)}
<div className="placeholder-message">{message} </div> <div className="placeholder-message">{message} </div>

View File

@ -1,24 +1,25 @@
.placeholder { .placeholder {
text-align: center; text-align: center;
margin: auto; margin: auto;
}
.placeholder-icon { > div:first-child {
display: block; display: block;
margin: auto auto 0.75rem; margin: auto auto 0.75rem;
} }
.placeholder-title { b {
margin-bottom: 0.25rem; margin-bottom: 0.25rem;
display: inline-block; display: inline-block;
} }
.placeholder-subtitle { p {
margin-bottom: 0.25rem; margin-bottom: 0.25rem;
max-width: 600px; max-width: 600px;
margin: auto; margin: auto;
} color: var(--codex-input-label-color);
}
.placeholder-button { .button {
margin: 0.75rem auto 0; margin: 0.75rem auto 0;
}
} }

View File

@ -1,14 +1,6 @@
import { ChangeEvent, CSSProperties } from "react"; import { ChangeEvent } from "react";
import "./select.css"; import "./select.css";
interface CustomStyleCSS extends CSSProperties {
"--codex-select-background"?: string;
"--codex-color"?: string;
"--codex-border-radius"?: string;
"--codex-select-border"?: string;
"--codex-select-icon-url"?: string;
}
type Props = { type Props = {
label: string; label: string;
@ -33,11 +25,6 @@ type Props = {
onMouseLeave?: () => void; onMouseLeave?: () => void;
/**
* Apply custom css variables.
*/
style?: CustomStyleCSS;
defaultValue?: string; defaultValue?: string;
value: string; value: string;
@ -54,36 +41,29 @@ export function Select({
onFocus, onFocus,
onMouseEnter, onMouseEnter,
onMouseLeave, onMouseLeave,
style,
className = "", className = "",
defaultValue, defaultValue,
value, value,
}: Props) { }: Props) {
return ( return (
<> <div className={"select " + className}>
<label htmlFor={id} className="select-label"> <label htmlFor={id}>{label}</label>
{label} <select
</label> id={id}
<div> onChange={onChange}
<select onBlur={onBlur}
id={id} onFocus={onFocus}
className={`select ${className}`} onMouseEnter={onMouseEnter}
onChange={onChange} onMouseLeave={onMouseLeave}
onBlur={onBlur} defaultValue={defaultValue}
onFocus={onFocus} value={value}
onMouseEnter={onMouseEnter} >
onMouseLeave={onMouseLeave} {options.map(([oval, text]) => (
style={style} <option key={oval} value={oval}>
defaultValue={defaultValue} {text}
value={value} </option>
> ))}
{options.map(([oval, text]) => ( </select>
<option key={oval} value={oval}> </div>
{text}
</option>
))}
</select>
</div>
</>
); );
} }

View File

@ -1,44 +1,46 @@
.select { .select {
appearance: none; select {
-moz-appearance: none; appearance: none;
-webkit-appearance: none; -moz-appearance: none;
background-color: var( -webkit-appearance: none;
--codex-select-background, background-color: var(
var(--codex-background-secondary) --codex-select-background,
); var(--codex-background-secondary)
outline: 2px solid transparent; );
outline-offset: 2px; outline: 2px solid transparent;
color: var(--codex-color); outline-offset: 2px;
border-radius: var(--codex-border-radius); color: var(--codex-color);
padding: 0.75rem 1rem; border-radius: var(--codex-border-radius);
padding-inline-end: 2.25rem; padding: 0.75rem 1rem;
transition: box-shadow 0.35s; padding-inline-end: 2.25rem;
border: var(--codex-select-border, 1px solid var(--codex-border-color)); transition: box-shadow 0.35s;
background-image: var( border: var(--codex-select-border, 1px solid var(--codex-border-color));
--codex-select-icon-url, background-image: var(
url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M8.35355 4.06066C8.15829 3.8654 7.84171 3.8654 7.64645 4.06066L5.35355 6.35355C5.15829 6.54882 4.84171 6.54882 4.64645 6.35355C4.45118 6.15829 4.45118 5.84171 4.64645 5.64645L6.93934 3.35356C7.52513 2.76777 8.47487 2.76777 9.06066 3.35355L11.3536 5.64645C11.5488 5.84171 11.5488 6.15829 11.3536 6.35355C11.1583 6.54882 10.8417 6.54882 10.6464 6.35355L8.35355 4.06066Z' fill='%236b7280'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M8.35355 11.9393C8.15829 12.1346 7.84171 12.1346 7.64645 11.9393L5.35355 9.64645C5.15829 9.45119 4.84171 9.45119 4.64645 9.64645C4.45118 9.84171 4.45118 10.1583 4.64645 10.3536L6.93934 12.6464C7.52513 13.2322 8.47487 13.2322 9.06066 12.6464L11.3536 10.3536C11.5488 10.1583 11.5488 9.84171 11.3536 9.64645C11.1583 9.45119 10.8417 9.45119 10.6464 9.64645L8.35355 11.9393Z' fill='%236b7280'/%3E%3C/svg%3E%0A") --codex-select-icon-url,
); url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M8.35355 4.06066C8.15829 3.8654 7.84171 3.8654 7.64645 4.06066L5.35355 6.35355C5.15829 6.54882 4.84171 6.54882 4.64645 6.35355C4.45118 6.15829 4.45118 5.84171 4.64645 5.64645L6.93934 3.35356C7.52513 2.76777 8.47487 2.76777 9.06066 3.35355L11.3536 5.64645C11.5488 5.84171 11.5488 6.15829 11.3536 6.35355C11.1583 6.54882 10.8417 6.54882 10.6464 6.35355L8.35355 4.06066Z' fill='%236b7280'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M8.35355 11.9393C8.15829 12.1346 7.84171 12.1346 7.64645 11.9393L5.35355 9.64645C5.15829 9.45119 4.84171 9.45119 4.64645 9.64645C4.45118 9.84171 4.45118 10.1583 4.64645 10.3536L6.93934 12.6464C7.52513 13.2322 8.47487 13.2322 9.06066 12.6464L11.3536 10.3536C11.5488 10.1583 11.5488 9.84171 11.3536 9.64645C11.1583 9.45119 10.8417 9.45119 10.6464 9.64645L8.35355 11.9393Z' fill='%236b7280'/%3E%3C/svg%3E%0A")
background-position: right 0.5rem center; );
background-repeat: no-repeat; background-position: right 0.5rem center;
background-size: 1.25em 1.25em; background-repeat: no-repeat;
box-sizing: border-box; background-size: 1.25em 1.25em;
} box-sizing: border-box;
.select:hover, @media (min-width: 801px) {
.select:focus-visible, & {
.select:active { min-width: 20rem;
box-shadow: 0 0 0 1px var(--codex-border-color); }
} }
.select-label { &:hover,
margin-bottom: 0.5rem; &:focus-visible,
font-weight: 500; &:active {
display: block; box-shadow: 0 0 0 1px var(--codex-border-color);
color: var(--codex-color); }
} }
@media (min-width: 801px) { label {
.select { margin-bottom: 0.5rem;
min-width: 20rem; font-weight: 500;
display: block;
color: var(--codex-color);
} }
} }

View File

@ -16,17 +16,10 @@ export function Sheets({ open, onClose, children }: Props) {
const attr = attributes({ "aria-expanded": open }); const attr = attributes({ "aria-expanded": open });
return ( return (
<div <div className={classnames(["sheets"], ["sheets--open", open])}>
className={classnames( <Backdrop onClose={onClose} open={open} />
["sheets-container"],
["sheets-container--open", open]
)}
>
<Backdrop onClose={onClose} open={open} className={"sheets-backdrop"} />
<div className="sheets" {...attr}> <aside {...attr}>{children}</aside>
{children}
</div>
</div> </div>
); );
} }

View File

@ -1,57 +1,53 @@
.sheets { .sheets {
position: fixed;
transition: transform 0.25s;
background-color: var(--codex-background-secondary);
z-index: 2;
justify-content: space-between;
}
.sheets-container {
overflow: hidden; overflow: hidden;
width: 100%; width: 100%;
height: 100%; height: 100%;
left: 0; left: 0;
top: 0; top: 0;
z-index: -1; z-index: -1;
}
.sheets-container--open { &.sheets--open {
z-index: 2; z-index: 2;
}
.sheets-backdrop {
z-index: -1;
}
@media (min-width: 1000px) {
.sheets {
width: 300px;
height: 100%;
bottom: 0;
top: 0;
transform: translatex(1300px);
right: 0;
} }
.sheets[aria-expanded] { aside {
transform: translatex(0); position: fixed;
z-index: 10; transition: transform 0.25s;
} background-color: var(--codex-background-secondary);
} z-index: 2;
justify-content: space-between;
@media (max-width: 999px) {
.sheets { @media (min-width: 1000px) {
width: 100%; & {
height: auto; width: 300px;
bottom: 0; height: 100%;
top: auto; bottom: 0;
transform: translatey(1300px); top: 0;
left: 0; transform: translatex(1300px);
padding-bottom: 1.5rem; right: 0;
} }
.sheets[aria-expanded] { &[aria-expanded] {
transform: translatey(0); transform: translatex(0);
z-index: 10; z-index: 10;
}
}
@media (max-width: 999px) {
& {
width: 100%;
height: auto;
bottom: 0;
top: auto;
transform: translatey(1300px);
left: 0;
padding-bottom: 1.5rem;
}
&[aria-expanded] {
transform: translatey(0);
z-index: 10;
}
}
} }
} }

View File

@ -1,66 +0,0 @@
import { CSSProperties, ReactNode } from "react";
import "./simpleText.css";
interface CustomStyleCSS extends CSSProperties {
"--codex-color"?: string;
"--codex-color-primary"?: string;
"--codex-color-light"?: string;
"--codex-color-error"?: string;
"--codex-color-warning"?: string;
}
type Props = {
/**
* Default variant is normal
*/
variant?: "normal" | "primary" | "light" | "error" | "warning";
className?: string;
children: string | ReactNode;
/**
* Apply custom css variables.
* --codex-color
* --codex-color-primary
* --codex-color-light
* --codex-color-error
* --codex-color-warning
*/
style?: CustomStyleCSS;
size?: "normal" | "small";
center?: boolean;
bold?: boolean;
onClick?: () => void;
};
export function SimpleText({
variant = "normal",
className = "",
center,
size = "normal",
onClick,
style,
children,
bold,
}: Props) {
const c = `text text--${variant} ${className} ${center ? "text--center" : ""} ${bold ? "text--bold" : ""}`;
if (size === "small") {
return (
<small onClick={onClick} className={c} style={style}>
{children}
</small>
);
}
return (
<span onClick={onClick} className={c} style={style}>
{children}
</span>
);
}

View File

@ -1,27 +0,0 @@
.text--normal {
color: var(--codex-color);
}
.text--primary {
color: var(--codex-color-primary);
}
.text--light {
color: var(--codex-input-label-color);
}
.text--center {
text-align: center;
}
.text--error {
color: rgb(var(--codex-color-error));
}
.text--warning {
color: rgb(var(--codex-color-warning));
}
.text--bold {
font-weight: bold;
}

View File

@ -1,4 +1,3 @@
import { SimpleText } from "../SimpleText/SimpleText";
import { PrettyBytes } from "../utils/bytes"; import { PrettyBytes } from "../utils/bytes";
import "./spaceAllocation.css"; import "./spaceAllocation.css";
@ -25,36 +24,31 @@ export function SpaceAllocation({ data }: Props) {
const total = data.reduce((acc, val) => acc + val.size, 0); const total = data.reduce((acc, val) => acc + val.size, 0);
return ( return (
<> <div className="space-allocation">
<div className="nodeSpaceAllocation-bar"> <header>
{data.map((d) => ( {data.map((d) => (
<span <span
key={d.title} key={d.title}
className={`nodeSpaceAllocation-barItem nodeSpaceAllocation-barQuota ${d.className || ""}`} className={`${d.className || ""}`}
style={{ style={{
width: (d.size / total) * 100 + "%", width: (d.size / total) * 100 + "%",
backgroundColor: d.color, backgroundColor: d.color,
}} }}
></span> ></span>
))} ))}
</div> </header>
<div className="nodeSpaceAllocation-legend"> <ul className="nodeSpaceAllocation-legend">
{data.map((d) => ( {data.map((d) => (
<div key={d.title} className={"nodeSpaceAllocation-legendRow"}> <li key={d.title}>
<div <span style={{ backgroundColor: d.color }}></span>
className={`nodeSpaceAllocation-legendItem nodeSpaceAllocation-quota`} <p>
style={{ backgroundColor: d.color }} <span>{d.title}</span>
></div> <small> {PrettyBytes(d.size)}</small>
<div className="nodeSpaceAllocation-legendItem-text"> </p>
<small>{d.title}</small> </li>
<SimpleText variant="light" size="small">
{PrettyBytes(d.size)}
</SimpleText>
</div>
</div>
))} ))}
</div> </ul>
</> </div>
); );
} }

View File

@ -1,48 +1,46 @@
.nodeSpaceAllocation-bar { .space-allocation {
height: 10px; header {
display: flex; height: 10px;
gap: 0.75rem; display: flex;
margin-bottom: 0.5rem; gap: 0.75rem;
} margin-bottom: 0.5rem;
.nodeSpaceAllocation-barQuota { span {
width: 10%; display: inline-block;
} height: 100%;
border-radius: var(--codex-border-radius);
}
}
.nodeSpaceAllocation-barItem { ul {
display: inline-block; display: flex;
height: 100%; gap: 0.75rem;
border-radius: var(--codex-border-radius); flex-wrap: wrap;
} padding-left: 0;
.nodeSpaceAllocation-barQuota-used { li {
border-top-left-radius: var(--codex-border-radius); display: flex;
border-bottom-left-radius: var(--codex-border-radius); align-items: center;
border-radius: var(--codex-border-radius); justify-content: space-between;
} gap: 0.5rem;
.nodeSpaceAllocation-legend { > span {
display: flex; width: 1rem;
gap: 0.75rem; height: 1rem;
flex-wrap: wrap; border-radius: var(--codex-border-radius);
} }
.nodeSpaceAllocation-legendItem { p {
width: 1rem; display: flex;
height: 1rem; flex-direction: column;
border-radius: var(--codex-border-radius); line-height: 1rem;
} margin: 0;
font-size: 12px;
.nodeSpaceAllocation-legendRow { small {
display: flex; color: var(--codex-input-label-color);
align-items: center; }
justify-content: space-between; }
margin-bottom: 0.75rem; }
gap: 0.5rem; }
}
.nodeSpaceAllocation-legendItem-text {
display: flex;
flex-direction: column;
line-height: 1rem;
} }

View File

@ -0,0 +1,119 @@
.step {
display: flex;
align-items: center;
gap: 0.5rem;
transition: opacity 0.35s;
&:not([disabled]):not(.step--active):hover {
cursor: pointer;
opacity: 0.8;
}
@media (min-width: 801px) {
&:not(:last-child) {
flex: 1;
}
}
> div:first-child {
display: flex;
align-items: center;
gap: 0.5rem;
transition: opacity 0.35s;
width: 1.75rem;
height: 1.75rem;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: background-color 0.35s;
background-color: var(
--codex-stepper-background,
var(--codex-background-light)
);
span {
display: flex;
align-items: center;
justify-content: center;
mix-blend-mode: difference;
}
}
&.step--active > div:first-child,
&.step--done > div:first-child {
background-color: var(--codex-color-primary);
}
div:nth-child(2) {
display: flex;
flex: 1;
position: relative;
place-items: center;
hr {
border: 0;
height: 1px;
flex: 1;
background-color: var(
--codex-stepper-background,
var(--codex-background-light)
);
position: relative;
margin-bottom: 8px;
@media (max-width: 800px) {
& {
display: none;
}
}
}
&::before {
background-color: var(--codex-color-primary);
height: 1px;
content: " ";
position: absolute;
top: 8px;
animation-duration: 1s;
animation-name: step-back;
animation-fill-mode: forwards;
opacity: 0;
/* animation-direction: reverse; */
}
span {
position: absolute;
top: 10px;
font-size: 14px;
@media (max-width: 800px) {
& {
display: none;
}
}
}
}
&.step--done {
div:nth-child(2) {
&::before {
background-color: var(--codex-color-primary);
display: inline-block;
animation-duration: 1s;
animation-name: step;
animation-fill-mode: forwards;
opacity: 1;
}
}
}
&.step--mounted {
div:nth-child(2) {
&::before {
opacity: 1;
}
}
}
}

View File

@ -2,6 +2,7 @@ import { Check } from "lucide-react";
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { attributes } from "../utils/attributes"; import { attributes } from "../utils/attributes";
import { classnames } from "../utils/classnames"; import { classnames } from "../utils/classnames";
import "./Step.css";
type StepProps = { type StepProps = {
/** /**
@ -52,36 +53,22 @@ export function Step({
return ( return (
<div <div
className={classnames( className={classnames(
["stepper-step", true], ["step", true],
["stepper-step-active", isActive] ["step--active", isActive],
["step--done", isDone],
["step--,mounted", mounted.current]
)} )}
onClick={() => onClick?.(step)} onClick={() => onClick?.(step)}
{...attributes({ disabled: !onClick })} {...attributes({ disabled: !onClick })}
> >
<div className="stepper-step-info"> <div>
<div <span>{isDone ? <Check size={"1.25rem"} /> : step + 1}</span>
className={classnames(
["stepper-number", true],
["stepper-number-active", isActive],
["stepper-number-done", isDone]
)}
>
<span className="stepper-numberValue">
{isDone ? <Check size={"1.25rem"} /> : step + 1}
</span>
</div>
</div> </div>
{!isLast && ( {!isLast && (
<div className="stepper-step-between"> <div>
<div <hr />
className={classnames( <span>{title}</span>
["stepper-separator", true],
["stepper-separator-done", isDone],
["stepper-separator-mounted", mounted.current]
)}
></div>
<span className={"stepper-text"}>{title}</span>
</div> </div>
)} )}
</div> </div>

View File

@ -1,17 +1,10 @@
import { Button } from "../Button/Button"; import { Button } from "../Button/Button";
import "./stepper.css"; import "./stepper.css";
import { CSSProperties, Dispatch, ReactNode } from "react"; import { Dispatch, ReactNode } from "react";
import { Spinner } from "../Spinner/Spinner"; import { Spinner } from "../Spinner/Spinner";
import { Step } from "./Step"; import { Step } from "./Step";
import { StepperAction, StepperState } from "./useStepperReducer"; import { StepperAction, StepperState } from "./useStepperReducer";
import { classnames } from "../utils/classnames";
interface CustomStyleCSS extends CSSProperties {
"--codex-background"?: string;
"--codex-border-radius"?: string;
"--codex-stepper-background": string;
"--codex-color-primary": string;
"--codex-border-color": string;
}
type Props = { type Props = {
/** /**
@ -24,8 +17,6 @@ type Props = {
*/ */
children: ReactNode; children: ReactNode;
style?: CustomStyleCSS;
/** /**
* The duration between steps * The duration between steps
*/ */
@ -69,7 +60,6 @@ export function Stepper({
titles, titles,
children, children,
state, state,
style,
dispatch, dispatch,
className = "", className = "",
backLabel = "Back", backLabel = "Back",
@ -95,8 +85,13 @@ export function Stepper({
}; };
return ( return (
<div className={"stepper " + className} style={style}> <div
<div className="stepper-steps"> className={classnames(
["stepper " + className],
["stepper--progress", state.progress]
)}
>
<header>
{titles.map((title, index) => ( {titles.map((title, index) => (
<Step <Step
title={title} title={title}
@ -108,19 +103,13 @@ export function Stepper({
onClick={state.step > index ? () => onChangeStep(index) : undefined} onClick={state.step > index ? () => onChangeStep(index) : undefined}
/> />
))} ))}
</div> </header>
<div className="stepper-body"> <main>
{state.progress ? ( {state.progress ? <Spinner width={"3rem"} /> : <>{children}</>}
<div className="stepper-progress"> </main>
<Spinner width={"3rem"} />
</div>
) : (
<>{children}</>
)}
</div>
<div className="stepper-buttons"> <footer>
<Button <Button
label={backLabel} label={backLabel}
variant="outline" variant="outline"
@ -132,7 +121,7 @@ export function Stepper({
onClick={() => onChangeStep(state.step + 1)} onClick={() => onChangeStep(state.step + 1)}
disabled={!state.isNextEnable} disabled={!state.isNextEnable}
/> />
</div> </footer>
</div> </div>
); );
} }

View File

@ -3,120 +3,46 @@
flex-direction: column; flex-direction: column;
background-color: var(--codex-background); background-color: var(--codex-background);
border-radius: var(--codex-border-radius); border-radius: var(--codex-border-radius);
}
.stepper-progress, header {
.stepper-step-info, display: flex;
.stepper-steps, align-items: center;
.stepper-step { gap: 0.5rem;
display: flex; transition: opacity 0.35s;
align-items: center; }
gap: 0.5rem;
transition: opacity 0.35s;
}
.stepper-step:not([disabled]):not(.stepper-separator-active):hover { main {
cursor: pointer; margin: 1.5rem 0;
opacity: 0.8; border: 1px dashed var(--codex-border-color);
} border-radius: var(--codex-border-radius);
background-color: var(
--codex-stepper-background,
var(--codex-background-light)
);
min-height: 200px;
padding: 1.5rem;
display: flex;
flex-direction: column;
.stepper-number { @media (min-width: 801px) {
width: 1.75rem; & {
height: 1.75rem; min-width: 500px;
display: flex; }
align-items: center; }
justify-content: center; }
border-radius: 50%;
transition: background-color 0.35s;
}
.stepper-number:not(.stepper-number-active):not(.stepper-number-done) { &.stepper--progress {
background-color: var( main {
--codex-stepper-background, justify-content: center;
var(--codex-background-light) flex: 1;
); align-items: center;
} }
}
.stepper-separator { footer {
height: 1px; display: flex;
flex: 1; justify-content: space-between;
background-color: var( }
--codex-stepper-background,
var(--codex-background-light)
);
position: relative;
}
.stepper-number-done,
.stepper-number-active {
background-color: var(--codex-color-primary);
}
.stepper-numberValue {
mix-blend-mode: difference;
}
.stepper-buttons {
display: flex;
justify-content: space-between;
}
.stepper-body {
margin: 1.5rem 0;
border: 1px dashed var(--codex-border-color);
border-radius: var(--codex-border-radius);
background-color: var(
--codex-stepper-background,
var(--codex-background-light)
);
min-height: 200px;
padding: 1.5rem;
display: flex;
flex-direction: column;
}
.stepper-progress {
justify-content: center;
flex: 1;
}
.stepper-numberValue {
display: flex;
align-items: center;
justify-content: center;
}
.stepper-separator::before {
background-color: var(--codex-color-primary);
height: 1px;
content: " ";
position: absolute;
top: 0;
animation-duration: 1s;
animation-name: step-back;
animation-fill-mode: forwards;
opacity: 0;
/* animation-direction: reverse; */
}
.stepper-separator-done::before {
background-color: var(--codex-color-primary);
display: inline-block;
animation-duration: 1s;
animation-name: step;
animation-fill-mode: forwards;
opacity: 1;
}
.stepper-separator-mounted::before {
opacity: 1;
}
.stepper-success {
display: flex;
flex: 1;
place-items: center;
flex-direction: column;
} }
@keyframes step { @keyframes step {
@ -138,40 +64,3 @@
width: 0%; width: 0%;
} }
} }
@media (min-width: 801px) {
.stepper-container {
width: 700px;
}
.stepper-step:not(:last-child) {
flex: 1;
}
.stepper-step-between {
display: flex;
flex: 1;
position: relative;
place-items: center;
}
.stepper-text {
position: absolute;
top: 5px;
}
.stepper-body {
min-width: 500px;
}
}
@media (max-width: 800px) {
.stepper-container {
width: 100%;
}
.stepper-step:not(.stepper-step-active) .stepper-text,
.stepper-separator {
display: none;
}
}

View File

@ -1,15 +0,0 @@
import { ReactNode } from "react";
import "./cell.css";
export type CellProps = {
children: ReactNode | string;
} & React.DetailedHTMLProps<
React.TdHTMLAttributes<HTMLTableCellElement>,
HTMLTableCellElement
>;
export const Cell = ({ children, className = "", ...rest }: CellProps) => (
<td className={"cell" + className} {...rest}>
{children}
</td>
);

View File

@ -1,18 +0,0 @@
import { Fragment, ReactElement } from "react";
import { Cell, CellProps } from "./Cell";
import "./row.css";
export type RowProps = {
cells: ReactElement<CellProps, typeof Cell>[];
className?: string;
};
export function Row({ cells, className = "" }: RowProps) {
return (
<tr className={"row " + className}>
{cells.map((Cell, index) => (
<Fragment key={index}>{Cell}</Fragment>
))}
</tr>
);
}

View File

@ -1,8 +1,8 @@
import "./table.css"; import "./table.css";
import { ArrowDownUp, Search } from "lucide-react"; import { ArrowDownUp, Search } from "lucide-react";
import { Row, RowProps } from "./Row"; import { Fragment, ReactElement, ReactNode, useEffect, useState } from "react";
import { Fragment, ReactElement, useEffect, useState } from "react";
import { classnames } from "../utils/classnames"; import { classnames } from "../utils/classnames";
import { attributes } from "../utils/attributes";
export type TabSortState = "asc" | "desc" | null; export type TabSortState = "asc" | "desc" | null;
@ -61,10 +61,16 @@ export function Table({
}; };
return ( return (
<div className={`table-container ${className}`}> <div
<table className={"table"}> className={classnames(
<thead className="table-thead"> ["table"],
<tr className="table-theadTr"> [className],
["table--empty", !!rows.length]
)}
>
<table>
<thead>
<tr>
{headers.map((col, index) => { {headers.map((col, index) => {
const [name, sort] = Array.isArray(col) ? col : [col]; const [name, sort] = Array.isArray(col) ? col : [col];
const state = index === sortSelected[0] ? sortSelected[1] : null; const state = index === sortSelected[0] ? sortSelected[1] : null;
@ -72,9 +78,14 @@ export function Table({
return ( return (
<th <th
className={classnames( {...attributes(
["table-theadTh"], sort
["table-theadTh--clickable", !!sort] ? {
role: "button",
"aria-sort":
state === "asc" ? "ascending" : "descending",
}
: {}
)} )}
key={name} key={name}
onClick={() => { onClick={() => {
@ -82,14 +93,9 @@ export function Table({
sort?.(nxt); sort?.(nxt);
}} }}
> >
<div className="table-theadTh-content"> <div>
<span>{name}</span> <span>{name}</span>
{sort && ( {sort && <ArrowDownUp size={"1rem"}></ArrowDownUp>}
<ArrowDownUp
className={"table-theadTh-icon--" + state}
size={"1rem"}
></ArrowDownUp>
)}
</div> </div>
</th> </th>
); );
@ -104,11 +110,39 @@ export function Table({
</table> </table>
{!rows.length && ( {!rows.length && (
<div className="table-placeholder"> <div>
<Search /> <Search />
<p className="table-placeholderText">No data.</p> <p>No data.</p>
</div> </div>
)} )}
</div> </div>
); );
} }
export type CellProps = {
children: ReactNode | string;
} & React.DetailedHTMLProps<
React.TdHTMLAttributes<HTMLTableCellElement>,
HTMLTableCellElement
>;
export const Cell = ({ children, className = "", ...rest }: CellProps) => (
<td className={className} {...rest}>
{children}
</td>
);
export type RowProps = {
cells: ReactElement<CellProps, typeof Cell>[];
className?: string;
};
export function Row({ cells, className = "" }: RowProps) {
return (
<tr className={className}>
{cells.map((Cell, index) => (
<Fragment key={index}>{Cell}</Fragment>
))}
</tr>
);
}

View File

@ -1,4 +0,0 @@
.cell {
text-align: left;
padding: 1rem;
}

View File

@ -1,8 +0,0 @@
.row {
border-bottom: 1px solid var(--codex-border-color);
transition: background-color 0.35s;
}
.row:hover {
background-color: var(--codex-background-light);
}

View File

@ -1,70 +1,131 @@
.table { .table {
border-collapse: collapse;
width: 100%;
}
.table-placeholder {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
margin-top: 2rem;
}
.table-placeholderText {
margin-top: 0.5rem;
margin-bottom: 0;
}
.table-container {
border: 1px solid var(--codex-border-color);
background-color: var(--codex-background-secondary);
padding: 2rem; padding: 2rem;
border-radius: var(--codex-border-radius); border-radius: var(--codex-border-radius);
overflow-x: auto; overflow-x: auto;
overflow-y: hidden; overflow-y: hidden;
}
.table-theadTr { table {
border-bottom: 1px solid var(--codex-border-color); border-spacing: 0 12px;
} width: 100%;
.table-theadTh { thead {
color: var(--codex-color-light); tr {
font-weight: normal; border-bottom: 1px solid var(--codex-border-color);
text-transform: uppercase; border-radius: 8px;
font-size: 0.9rem;
text-align: left;
padding: 1rem;
}
.table-theadTh--clickable { th {
cursor: pointer; color: var(--codex-color-light);
} font-weight: normal;
text-transform: uppercase;
font-size: 0.9rem;
text-align: left;
height: 36px;
padding: 0 16px;
box-sizing: border-box;
background-color: #232323;
.table-theadTh-content { &[role="button"] {
display: flex; cursor: pointer;
align-items: center; }
gap: 0.5rem;
}
.table-theadTh-content svg { div {
position: relative; display: flex;
top: -2px; align-items: center;
cursor: pointer; gap: 0.5rem;
} }
.table-theadTh-content svg path { svg {
opacity: 0.5; position: relative;
transition: opacity 0.35s; top: -2px;
} cursor: pointer;
}
.table-theadTh-icon--desc path:nth-child(1), svg path {
.table-theadTh-icon--desc path:nth-child(2) { opacity: 0.5;
opacity: 1; transition: opacity 0.35s;
} }
.table-theadTh-icon--asc path:nth-child(3), th[aria-sort="descending"] path:nth-child(1),
.table-theadTh-icon--asc path:nth-child(4) { th[aria-sort="descending"] path:nth-child(2) {
opacity: 1; opacity: 1;
}
th[aria-sort="ascending"] path:nth-child(3),
th[aria-sort="ascending"] path:nth-child(4) {
opacity: 1;
}
&:first-child {
border-top-left-radius: 8px;
border-bottom-left-radius: 8px;
}
&:last-child {
border-top-right-radius: 8px;
border-bottom-right-radius: 8px;
}
}
}
}
tbody {
tr {
border-bottom: 1px solid var(--codex-border-color);
transition: background-color 0.35s;
border-radius: 8px;
position: relative;
&:hover {
background-color: var(--codex-background-light);
}
td {
text-align: left;
height: 64px;
box-sizing: border-box;
background-color: #232323;
padding: 0 16px;
font-family: Inter;
font-size: 14px;
font-weight: 400;
line-height: 20px;
letter-spacing: -0.006em;
&:first-child {
border-top-left-radius: 8px;
border-bottom-left-radius: 8px;
}
&:last-child {
border-top-right-radius: 8px;
border-bottom-right-radius: 8px;
}
}
&::after {
content: " ";
width: 100%;
height: 1px;
display: inline-block;
background: #96969633;
position: absolute;
bottom: -7px;
left: 0;
}
}
}
}
> div {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
margin-top: 2rem;
p {
margin-top: 0.5rem;
margin-bottom: 0;
}
}
} }

View File

@ -1,6 +1,7 @@
import { ComponentType } from "react"; import { ComponentType } from "react";
import "./tabs.css"; import "./tabs.css";
import { classnames } from "../utils/classnames"; import { classnames } from "../utils/classnames";
import { attributes } from "../utils/attributes";
export type TabProps = { export type TabProps = {
label: string; label: string;
@ -26,11 +27,8 @@ export function Tabs({ tabs, onTabChange, tabIndex }: Props) {
{tabs.map((tab, index) => ( {tabs.map((tab, index) => (
<div <div
key={tab.label} key={tab.label}
className={classnames( {...attributes({ "aria-selected": tabIndex === index })}
["tabs-tab"], className={classnames([tab.className || ""])}
["tabs-tab--active", tabIndex === index],
[tab.className || ""]
)}
onClick={() => onTabChange(index)} onClick={() => onTabChange(index)}
> >
{tab.Icon && <tab.Icon />} {tab.Icon && <tab.Icon />}

View File

@ -3,50 +3,39 @@
margin-top: 1rem; margin-top: 1rem;
gap: 1rem; gap: 1rem;
position: relative; position: relative;
}
.tabs-tab { &::after {
display: flex; width: 100%;
align-items: center; background-color: var(--codex-background-light);
gap: 0.25rem; content: " ";
padding-bottom: 1rem; position: absolute;
cursor: pointer; height: 2px;
transition: 0.35s opacity; top: 11px;
z-index: 1; top: 31px;
position: relative; }
}
.tabs::after { div {
width: 100%; display: flex;
background-color: var(--codex-background-light); align-items: center;
content: " "; gap: 0.25rem;
position: absolute; padding-bottom: 1rem;
height: 2px; cursor: pointer;
top: 11px; transition: 0.35s opacity;
top: 31px; z-index: 1;
} position: relative;
.tabs-tab:not(.files-headerTab--active) { &:hover {
opacity: 0.7; opacity: 0.85;
} }
.tabs-tab:hover { &[aria-selected]:after {
opacity: 0.85; width: 100%;
} background-color: var(--codex-color-contrast);
content: " ";
.tabs-tab--active:after { position: absolute;
width: 100%; height: 2px;
background-color: var(--codex-color-contrast); top: 11px;
content: " "; top: 31px;
position: absolute; }
height: 2px; }
top: 11px;
top: 31px;
}
.tabs-icon {
width: 1rem;
height: 1rem;
display: flex;
place-items: center;
} }

View File

@ -1,16 +1,9 @@
import { CSSProperties, useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { attributes } from "../utils/attributes"; import { attributes } from "../utils/attributes";
import "./toast.css"; import "./toast.css";
import { CircleCheck, CircleX, Info, X } from "lucide-react"; import { CircleCheck, CircleX, Info, X } from "lucide-react";
import { ButtonIcon } from "../ButtonIcon/ButtonIcon"; import { ButtonIcon } from "../ButtonIcon/ButtonIcon";
interface CustomStyleCSS extends CSSProperties {
"--codex-toast-background"?: string;
"--codex-toast-border-color"?: string;
"--codex-border-radius"?: string;
"--codex-toast-color"?: string;
}
type Props = { type Props = {
message: string; message: string;
@ -30,22 +23,12 @@ type Props = {
className?: string; className?: string;
/**
* Apply custom css variables.
* codex-toast-background
* codex-toast-border-color
* codex-border-radius
* codex-toast-color
*/
style?: CustomStyleCSS;
variant: "success" | "error" | "default"; variant: "success" | "error" | "default";
}; };
export function Toast({ export function Toast({
message, message,
time, time,
style,
variant, variant,
className = "", className = "",
duration = 3000, duration = 3000,
@ -84,12 +67,11 @@ export function Toast({
<div <div
className={`toast ${className} toast--${variant}`} className={`toast ${className} toast--${variant}`}
{...attributes({ "aria-hidden": time == 0 || msg === "" })} {...attributes({ "aria-hidden": time == 0 || msg === "" })}
style={style}
> >
<Icon size="1.25rem" className="toast-icon" /> <Icon size="1.25rem" />
<span> <span>
<b className="toast-title">{variant} ! </b> <b>{variant} ! </b>
<span>{msg}</span> <span>{msg}</span>
</span> </span>

View File

@ -18,33 +18,33 @@
border: 1px solid rgb(var(--codex-toast-color)); border: 1px solid rgb(var(--codex-toast-color));
background: rgba(var(--codex-toast-color), 1); background: rgba(var(--codex-toast-color), 1);
z-index: 20; z-index: 20;
}
.toast-close { &[aria-hidden] {
margin-left: 0.75rem; transform: translateX(1000px);
} }
.toast[aria-hidden] { &.toast--success {
transform: translateX(1000px); --codex-toast-color: var(--codex-color-success);
} }
.toast-title { &.toast--error {
text-transform: capitalize; --codex-toast-color: var(--codex-color-error);
} }
.toast-icon { &.toast--default {
fill: rgba(var(--codex-toast-color), 0.2); --codex-toast-color: var(--codex-color-grey);
stroke: white; }
}
.toast--success { .button {
--codex-toast-color: var(--codex-color-success); margin-left: 0.75rem;
} }
.toast--error { b {
--codex-toast-color: var(--codex-color-error); text-transform: capitalize;
} }
.toast--default { svg {
--codex-toast-color: var(--codex-color-grey); fill: rgba(var(--codex-toast-color), 0.2);
stroke: white;
}
} }

View File

@ -1,5 +1,5 @@
import { FileStack, Upload as UploadIcon } from "lucide-react"; import { FileStack, Upload as UploadIcon } from "lucide-react";
import { ChangeEvent, CSSProperties, DragEventHandler, useRef } from "react"; import { ChangeEvent, DragEventHandler, useRef } from "react";
import { attributes } from "../utils/attributes.ts"; import { attributes } from "../utils/attributes.ts";
import "./upload.css"; import "./upload.css";
import { UploadFile } from "./UploadFile.tsx"; import { UploadFile } from "./UploadFile.tsx";
@ -7,17 +7,6 @@ import { useUploadStategy } from "./useUploadStrategy.ts";
import { classnames } from "../utils/classnames.ts"; import { classnames } from "../utils/classnames.ts";
import { ButtonIcon } from "../ButtonIcon/ButtonIcon.tsx"; import { ButtonIcon } from "../ButtonIcon/ButtonIcon.tsx";
import { CodexData } from "@codex-storage/sdk-js"; import { CodexData } from "@codex-storage/sdk-js";
import { SimpleText } from "../SimpleText/SimpleText.tsx";
interface CustomStyleCSS extends CSSProperties {
"--codex-border-color"?: string;
"--codex-border-radius"?: string;
"--codex-upload-background"?: string;
"--codex-color-primary"?: string;
"--codex-color"?: string;
"--codex-color-error"?: string;
"--codex-color-warning"?: string;
}
type Props = { type Props = {
/** /**
@ -68,18 +57,6 @@ type Props = {
*/ */
// useWorker?: boolean; // useWorker?: boolean;
/**
* Apply custom css variables.
* --codex-border-color
* --codex-border-radius
* --codex-upload-background
* --codex-color-primary
* --codex-color
* --codex-color-error
* --codex-color-warning
*/
style?: CustomStyleCSS;
/** /**
* Success message displayed when a file is updated. * Success message displayed when a file is updated.
* Default: File uploaded successfully. * Default: File uploaded successfully.
@ -151,17 +128,16 @@ export function Upload({
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
> >
<ButtonIcon Icon={multiple ? FileStack : UploadIcon}></ButtonIcon> <ButtonIcon Icon={multiple ? FileStack : UploadIcon}></ButtonIcon>
<div className="upload-text">
<div> <p>
<b> <b>
Drop your {multiple ? "file(s)" : "file"} here or{" "} Drop your {multiple ? "file(s)" : "file"} here or{" "}
<span className="text--primary">browse</span> <span>browse</span>
</b> </b>
</div> </p>
<SimpleText size="small" variant="light" center>
{multiple ? "Up to 10 files" : "Choose one single file"} <small> {multiple ? "Up to 10 files" : "Choose one single file"}</small>
</SimpleText>
</div>
<input <input
data-testid="upload" data-testid="upload"
type="file" type="file"
@ -171,7 +147,7 @@ export function Upload({
{...attributes({ multiple: multiple })} {...attributes({ multiple: multiple })}
/> />
{warning && <SimpleText variant="warning">{warning}</SimpleText>} {warning && <span>{warning}</span>}
</div> </div>
{files.map(({ id, file }) => ( {files.map(({ id, file }) => (

View File

@ -0,0 +1,117 @@
.upload-file {
&[aria-invalid] {
--codex-upload-color: rgb(var(--codex-color-error));
}
&[data-done] {
--codex-upload-color: var(--codex-color-primary);
}
> div {
background-color: var(
--codex-upload-background,
var(--codex-background-secondary)
);
border-radius: var(--codex-border-radius);
border: 1px solid var(--codex-border-color);
padding: 1em 2rem;
margin-top: 0.5rem;
header {
flex-grow: 1;
gap: 0.5rem;
display: flex;
align-items: center;
> div:first-child {
display: flex;
align-items: center;
gap: 0.5rem;
flex-grow: 1;
img {
border-radius: var(--codex-border-radius);
}
p {
flex-grow: 1;
margin: 0;
b {
display: flex;
align-items: center;
color: var(--codex-upload-color);
span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 150px;
display: inline-block;
}
}
}
}
> div:nth-child(2) {
display: flex;
align-items: center;
justify-content: space-around;
gap: 0.25rem;
}
svg {
color: var(--codex-upload-color);
}
}
}
main {
display: flex;
align-items: center;
gap: 0.5rem;
margin: 0.25 0;
progress {
flex-grow: 1;
background-color: var(
--codex-upload-background,
var(--codex-background-secondary)
);
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border: none;
border-radius: var(--codex-border-radius);
height: 0.5rem;
width: 100%;
border: none;
&[value]::-webkit-progress-bar {
background-color: var(--codex-background-light);
border-radius: 50px;
}
&[value]::-webkit-progress-value {
background: var(--codex-upload-color);
border-radius: 50px;
}
&[value]::-moz-progress-bar {
border-radius: 50px;
background: var(--codex-upload-color);
}
}
span {
white-space: nowrap;
display: inline-block;
text-align: right;
}
}
footer {
font-size: 0.85rem;
color: var(--codex-upload-color);
}
}

View File

@ -7,7 +7,7 @@ import { Spinner } from "../Spinner/Spinner";
import { CodexData } from "@codex-storage/sdk-js"; import { CodexData } from "@codex-storage/sdk-js";
import { WebFileIcon } from "../WebFileIcon/WebFileIcon"; import { WebFileIcon } from "../WebFileIcon/WebFileIcon";
import { ButtonIcon } from "../ButtonIcon/ButtonIcon"; import { ButtonIcon } from "../ButtonIcon/ButtonIcon";
import { SimpleText } from "../SimpleText/SimpleText"; import "./UploadFile.css";
type UploadFileProps = { type UploadFileProps = {
file: File; file: File;
@ -263,37 +263,32 @@ export function UploadFile({
const ActionIcon = () => <UploadActionIcon status={status} />; const ActionIcon = () => <UploadActionIcon status={status} />;
return ( return (
<> <div
<div className={"uploadFile"}> className="upload-file"
<div className="uploadFile-info"> {...attributes({
<div className="uploadFile-infoLeft"> "aria-invalid": status === "error",
"data-done": status === "done",
})}
>
<div>
<header>
<div>
{preview ? ( {preview ? (
<img <img src={preview} width="24" alt="Preview" />
src={preview}
width="24"
alt="Preview"
className="uploadFile-preview"
/>
) : ( ) : (
<WebFileIcon type={file.type} /> <WebFileIcon type={file.type} />
)} )}
<div className="uploadFile-infoText"> <p>
<b <b>
className="uploadFile-name" <span>{filename}</span>
{...attributes({
"aria-invalid": status === "error",
"data-done": status === "done",
})}
>
<span className="uploadFile-filename">{filename}</span>
{extension && <span>.{extension}</span>} {extension && <span>.{extension}</span>}
</b> </b>
<div> <div>
<small>{PrettyBytes(file.size)}</small> <small>{PrettyBytes(file.size)}</small>
</div> </div>
</div> </p>
</div> </div>
<div className="uploadFile-infoRight"> <div>
<UploadStatusIcon status={status} /> <UploadStatusIcon status={status} />
<ButtonIcon <ButtonIcon
@ -302,33 +297,27 @@ export function UploadFile({
Icon={ActionIcon} Icon={ActionIcon}
></ButtonIcon> ></ButtonIcon>
</div> </div>
</div> </header>
<div className="uploadFile-progress"> <main>
<progress <progress
className="uploadFile-progressBar"
{...attributes({ {...attributes({
max: file ? progress.total.toString() : false, max: file ? progress.total.toString() : false,
value: file ? progress.loaded.toString() : false, value: file ? progress.loaded.toString() : false,
"aria-invalid": status === "error",
})} })}
/> />
<span className="uploadFile-progressBarPercent"> <span>{percent.toFixed(2)} %</span>
{percent.toFixed(2)} % </main>
</span>
</div>
<div className="uploadFile-message"> <footer>
{cid ? ( {cid ? (
<div className="text--primary">{successMessage}</div> <span>{successMessage}</span>
) : ( ) : (
<SimpleText variant="error"> <span> {error ? error : <>&nbsp;</>}</span>
{error ? error : <>&nbsp;</>}
</SimpleText>
)} )}
</div> </footer>
</div> </div>
</> </div>
); );
} }

View File

@ -11,172 +11,24 @@
gap: 0.5rem; gap: 0.5rem;
cursor: pointer; cursor: pointer;
padding: 0.5rem 2rem; padding: 0.5rem 2rem;
}
.upload-selected {
border-color: var(--codex-color-primary);
}
.uploadFile {
background-color: var(
--codex-upload-background,
var(--codex-background-secondary)
);
border-radius: var(--codex-border-radius);
border: 1px solid var(--codex-border-color);
padding: 1em 2rem;
margin-top: 0.5rem;
}
.uploadFile-info {
flex-grow: 1;
gap: 0.5rem;
}
.upload-actions {
gap: 0.5rem;
}
.upload-action-close {
color: var(--codex-color);
border: 1px solid var(--codex-color);
}
.upload-action-confirm {
color: var(--codex-color-primary);
border: 1px solid var(--codex-color-primary);
}
.uploadFile-progressBar {
flex-grow: 1;
background-color: var(
--codex-upload-background,
var(--codex-background-secondary)
);
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border: none;
border-radius: var(--codex-border-radius);
}
.uploadFile-progressBar {
height: 0.5rem;
width: 100%;
border: none;
}
.uploadFile-progressBar[value]::-webkit-progress-bar {
background-color: var(--codex-background-light);
border-radius: 50px;
}
.uploadFile-progressBar[value]::-webkit-progress-value {
background: var(--codex-color-primary);
border-radius: 50px;
}
.uploadFile-progressBar[value]::-moz-progress-bar {
border-radius: 50px;
background: var(--codex-color-primary);
}
.uploadFile-progressBar[aria-invalid]::-moz-progress-bar {
background: rgb(var(--codex-color-error));
}
.uploadFile-progressBar[aria-invalid]::-webkit-progress-value {
background: rgb(var(--codex-color-error));
}
.uploadFile-filename {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 150px;
display: inline-block;
}
.uploadFile-progressBarPercent {
white-space: nowrap;
display: inline-block;
text-align: right;
}
.uploadFile-message {
font-size: 0.85rem;
}
.upload-progress-check {
color: var(--codex-color-primary);
}
.upload-progress-cancelled {
color: rgb(var(--codex-color-error));
}
.uploadFile-preview {
border-radius: var(--codex-border-radius);
}
.uploadFile-infoLeft {
gap: 0.5rem;
flex-grow: 1;
}
.uploadFile-progress {
gap: 0.5rem;
}
.uploadFile-progress {
margin: 0.25 0;
}
.uploadFile-infoRight,
.uploadFile-progress,
.upload,
.uploadFile-info,
.uploadFile-infoLeft,
.uploadFile-name {
display: flex; display: flex;
align-items: center; align-items: center;
}
.uploadFile-name[aria-invalid] { p {
color: rgb(var(--codex-color-error)); margin-bottom: 0;
} margin-top: 12px;
line-height: 4px;
.uploadFile-name[data-done] { span {
color: var(--codex-color-primary); color: var(--codex-color-primary);
} }
}
.uploadFile-infoText { small {
flex-grow: 1; color: var(--codex-input-label-color);
} }
.uploadFile-infoRight { input + span {
justify-content: space-around; color: var(--codex-color-warning);
gap: 0.25rem; }
}
.uploadFile-progressBarPercent,
.uploadFile-right {
width: 5rem;
}
.uploadFile-cid {
transition: color 0.35s;
cursor: pointer;
}
.uploadFile-cid:hover {
color: var(--codex-color-primary);
}
.upload-warning {
border-color: rgb(var(--codex-color-warning));
}
.upload-text {
text-align: center;
} }

View File

@ -2,7 +2,6 @@ export { Button } from "./components/Button/Button";
export { ButtonIcon } from "./components/ButtonIcon/ButtonIcon"; export { ButtonIcon } from "./components/ButtonIcon/ButtonIcon";
export { Input } from "./components/Input/Input"; export { Input } from "./components/Input/Input";
export { InputGroup } from "./components/InputGroup/InputGroup"; export { InputGroup } from "./components/InputGroup/InputGroup";
export { SimpleText } from "./components/SimpleText/SimpleText";
export { Upload } from "./components/Upload/Upload"; export { Upload } from "./components/Upload/Upload";
export { Card } from "./components/Card/Card"; export { Card } from "./components/Card/Card";
export { Select } from "./components/Select/Select"; export { Select } from "./components/Select/Select";
@ -16,10 +15,7 @@ export { Spinner } from "./components/Spinner/Spinner";
export { WebFileIcon } from "./components/WebFileIcon/WebFileIcon"; export { WebFileIcon } from "./components/WebFileIcon/WebFileIcon";
export { Stepper } from "./components/Stepper/Stepper"; export { Stepper } from "./components/Stepper/Stepper";
export { Backdrop } from "./components/Backdrop/Backdrop"; export { Backdrop } from "./components/Backdrop/Backdrop";
export { Cell, type CellProps } from "./components/Table/Cell"; export * from "./components/Table/Table";
export { Table, type TabSortState } from "./components/Table/Table";
export { Row, type RowProps } from "./components/Table/Row";
export { NetworkIndicator } from "./components/NetworkIndicator/NetworkIndicator";
export { Tooltip } from "./components/Tooltip/Tooltip"; export { Tooltip } from "./components/Tooltip/Tooltip";
export { Collapse } from "./components/Collapse/Collapse"; export { Collapse } from "./components/Collapse/Collapse";
export { Placeholder } from "./components/Placeholder/Placeholder"; export { Placeholder } from "./components/Placeholder/Placeholder";

View File

@ -65,7 +65,12 @@ const ActionTemplate = (props: Props) => {
return ( return (
<div style={{ padding: "6rem" }}> <div style={{ padding: "6rem" }}>
<button onClick={onOpen}>Make Modal</button> <button onClick={onOpen}>Make Modal</button>
<Modal onClose={onClose} open={open} onAction={onAction}> <Modal
onClose={onClose}
open={open}
onAction={onAction}
displayActionButton={true}
>
<p>Hello world</p> <p>Hello world</p>
</Modal> </Modal>
</div> </div>

View File

@ -1,29 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import { NetworkIndicator } from "../src/components/NetworkIndicator/NetworkIndicator";
const meta = {
title: "Components/NetworkIndicator",
component: NetworkIndicator,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {},
} satisfies Meta<typeof NetworkIndicator>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Online: Story = {
args: {
online: true,
text: "Online",
},
};
export const Offline: Story = {
args: {
online: false,
text: "Offline",
},
};

View File

@ -20,14 +20,17 @@ export const Default: Story = {
{ {
title: "Space allocated", title: "Space allocated",
size: 10000000, size: 10000000,
color: "red"
}, },
{ {
title: "New space allocation", title: "New space allocation",
size: 10000000 * 0.2, size: 10000000 * 0.2,
color: "yellow"
}, },
{ {
title: "Remaining space", title: "Remaining space",
size: 10000000 * 0.2, size: 10000000 * 0.2,
color: "green"
}, },
], ],
}, },

View File

@ -1,7 +1,6 @@
import type { Meta, StoryObj } from "@storybook/react"; import type { Meta, StoryObj } from "@storybook/react";
import { Table } from "../src/components/Table/Table"; import { Table, Row } from "../src/components/Table/Table";
import "./Table.stories.css"; import "./Table.stories.css";
import { Row } from "../src/components/Table/Row";
import { Cell } from "../src"; import { Cell } from "../src";
const meta = { const meta = {