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",
"version": "0.0.27",
"version": "0.0.28",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@codex-storage/marketplace-ui-components",
"version": "0.0.27",
"version": "0.0.28",
"license": "MIT",
"dependencies": {
"lucide-react": "^0.453.0"
@ -41,6 +41,7 @@
},
"peerDependencies": {
"@codex-storage/sdk-js": ">=0.0.12",
"postcss-nesting": "^13.0.1",
"react": "^18.3.1",
"react-dom": "^18.3.1"
}
@ -628,6 +629,50 @@
"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": {
"version": "0.23.1",
"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==",
"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": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
@ -5558,7 +5615,6 @@
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"dev": true,
"funding": [
{
"type": "github",
@ -5824,8 +5880,7 @@
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
},
"node_modules/picomatch": {
"version": "2.3.1",
@ -5939,7 +5994,6 @@
"version": "8.4.47",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
"dev": true,
"funding": [
{
"type": "opencollective",
@ -5964,6 +6018,46 @@
"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": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@ -6673,7 +6767,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@ -7134,8 +7227,7 @@
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/utils-merge": {
"version": "1.0.1",

View File

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

View File

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

View File

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

View File

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

View File

@ -1,20 +1,7 @@
import { ComponentType, CSSProperties } from "react";
import { ComponentType } from "react";
import "./button.css";
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 = {
/**
* Button style variant. Default is primary.
@ -50,21 +37,6 @@ type Props = {
* Apply custom classname.
*/
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({
@ -75,7 +47,6 @@ export function Button({
onMouseLeave,
fetching = false,
disabled = false,
style,
variant = "primary",
onClick,
}: Props) {
@ -84,7 +55,6 @@ export function Button({
onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
style={style}
className={`button ${className} button--${variant}`}
{...attributes({
disabled: disabled || fetching,
@ -93,11 +63,11 @@ export function Button({
})}
>
{Icon && (
<div className="button-icon">
<div>
<Icon />
</div>
)}
<span className="button-label">{label}</span>
<span>{label}</span>
</button>
);
}

View File

@ -14,84 +14,84 @@
font-family: var(--codex-font-family);
border: 1px solid transparent;
place-content: center;
}
.button--primary {
background-color: var(--codex-color-primary);
color: var(--codex-color-on-primary);
}
.button:disabled {
cursor: not-allowed;
}
.button--primary:disabled {
background-color: var(--codex-color-disabled);
}
.button--outline:disabled {
color: var(--codex-color-disabled);
}
.button-label {
display: flex;
align-items: center;
gap: 0.75rem;
}
.button--outline {
color: var(--codex-color-outline, var(--codex-color-contrast));
border-color: var(--codex-border-color);
border-width: 1px;
border-style: solid;
background-color: transparent;
}
.button[aria-busy] {
cursor: wait;
}
.button-icon {
width: 16px;
height: 16px;
display: flex;
place-items: center;
}
.button[aria-busy]::after {
content: " ";
display: block;
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>')
);
position: absolute;
background-color: var(
--codex-button-background-busy,
var(--codex-background-backdrop)
);
/**
* Set full size and add border.
*/
width: calc(100% + 2px);
height: calc(100% + 2px);
background-repeat: no-repeat;
background-position: center;
background-size: 28px;
left: -1px;
right: 0;
border-radius: var(--codex-border-radius);
}
.button--primary:not(:disabled):hover {
cursor: pointer;
box-shadow: 0 0 0 3px
var(--codex-button-color-box-shadow, var(--codex-color-primary-variant));
}
.button--outline:not(:disabled):hover {
cursor: pointer;
box-shadow: 0 0 0 2px var(--codex-border-color);
&[aria-busy] {
cursor: wait;
}
&[aria-busy]::after {
content: " ";
display: block;
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>')
);
position: absolute;
background-color: var(
--codex-button-background-busy,
var(--codex-background-backdrop)
);
/**
* Set full size and add border.
*/
width: calc(100% + 2px);
height: calc(100% + 2px);
background-repeat: no-repeat;
background-position: center;
background-size: 28px;
left: -1px;
right: 0;
border-radius: var(--codex-border-radius);
}
&.button--primary:not(:disabled):hover {
cursor: pointer;
box-shadow: 0 0 0 3px
var(--codex-button-color-box-shadow, var(--codex-color-primary-variant));
}
&.button--outline:not(:disabled):hover {
cursor: pointer;
box-shadow: 0 0 0 2px var(--codex-border-color);
}
&.button--primary {
background-color: var(--codex-color-primary);
color: var(--codex-color-on-primary);
}
&:disabled {
cursor: not-allowed;
}
&.button--primary:disabled {
background-color: var(--codex-color-disabled);
}
&.button--outline:disabled {
color: var(--codex-color-disabled);
}
&.button--outline {
color: var(--codex-color-outline, var(--codex-color-contrast));
border-color: var(--codex-border-color);
border-width: 1px;
border-style: solid;
background-color: transparent;
}
span {
display: flex;
align-items: center;
gap: 0.75rem;
}
div {
width: 16px;
height: 16px;
display: flex;
place-items: center;
}
}

View File

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

View File

@ -10,41 +10,52 @@
cursor: pointer;
transition: box-shadow 0.35s;
border: none;
}
.buttonIcon--big {
width: 4rem;
height: 4rem;
}
&.buttonIcon--big {
width: 4rem;
height: 4rem;
}
.buttonIcon--small {
width: 2rem;
height: 2rem;
}
&.buttonIcon--small {
width: 2rem;
height: 2rem;
}
.buttonIcon svg {
mix-blend-mode: difference;
}
&:not(:disabled):hover {
cursor: pointer;
box-shadow: 0 0 0 2px var(--codex-border-color);
}
.buttonIcon:not(:disabled):hover {
cursor: pointer;
box-shadow: 0 0 0 2px var(--codex-border-color);
}
&:disabled {
color: var(--codex-color-disabled);
cursor: not-allowed;
}
.buttonIcon:disabled {
color: var(--codex-color-disabled);
cursor: not-allowed;
}
&.buttonIcon--buzz {
-webkit-animation-name: buzz;
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 {
-webkit-animation-name: buzz;
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--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;
}
svg {
mix-blend-mode: difference;
}
}
@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 {
0% {
-webkit-transform: translateY(0) scale(1);

View File

@ -1,33 +1,19 @@
import { CSSProperties, ReactNode } from "react";
import { ReactNode } from "react";
import "./card.css";
interface CustomStyleCSS extends CSSProperties {
"--codex-border-radius"?: string;
"--codex-border-color"?: string;
"--codex-font-family"?: string;
}
type Props = {
children: ReactNode;
className?: 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 (
<div className={`card ${className}`} style={style}>
<div className="card-header">{title}</div>
<div className="card-body">{children}</div>
<div className={`card ${className}`}>
<header>{title}</header>
<div>{children}</div>
</div>
);
}

View File

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

View File

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

View File

@ -1,85 +1,83 @@
.dropdown {
position: relative;
}
label {
margin-bottom: 0.5rem;
font-weight: 500;
display: block;
color: var(--codex-color);
}
.dropdown-panel {
position: absolute;
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;
}
> div {
position: relative;
}
.dropdown-label {
margin-bottom: 0.5rem;
font-weight: 500;
display: block;
color: var(--codex-color);
}
ul {
position: absolute;
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-panel[aria-expanded] {
transform: translateY(0.5rem);
opacity: 1;
z-index: 10;
}
&[aria-expanded] {
transform: translateY(0rem);
opacity: 1;
z-index: 10;
}
.dropdown-input {
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;
}
li {
padding: 0.5rem;
border-radius: var(--codex-border-radius);
transition: background-color 0.35s;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.75rem;
.dropdown-input:focus {
z-index: 11;
}
&:hover {
background-color: var(
--codex-dropdown-option-background-hover,
var(--codex-background-light)
);
}
.dropdown-option {
padding: 0.5rem;
border-radius: var(--codex-border-radius);
transition: background-color 0.35s;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.75rem;
}
span {
display: block;
word-break: break-all;
}
.dropdown-option:hover {
background-color: var(
--codex-dropdown-option-background-hover,
var(--codex-background-light)
);
}
span + span {
mix-blend-mode: difference;
font-size: 0.75rem;
}
}
}
.dropdown-noResults {
padding: 0.75rem 0.25rem;
}
.input {
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 {
display: block;
}
&:focus {
z-index: 11;
}
}
.dropdown-subtitle {
mix-blend-mode: difference;
font-size: 0.75rem;
}
.dropdown-title,
.dropdown-subtitle {
word-break: break-all;
p {
padding: 0.75rem 0.25rem;
}
}

View File

@ -1,12 +1,5 @@
import "./failure.css";
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 = {
/**
@ -29,14 +22,6 @@ type Props = {
* The button label
*/
button?: string;
/**
* Apply custom css variables.
* --codex-code-font-size
* --codex-text-contrast
* --codex-font-family
*/
style?: CustomStyleCSS;
};
export function Failure({
@ -48,9 +33,9 @@ export function Failure({
}: Props) {
return (
<div className="failure">
<h1 className="failure-code">{code}</h1>
<h2 className="failure-title">{title}</h2>
<div className="failure-message">{message}</div>
<h1>{code}</h1>
<h2>{title}</h2>
<div>{message}</div>
{onClick && <Button label={button} onClick={onClick} />}
</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 {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
flex: 1;
}
.failure-title {
font-size: 1.875rem;
line-height: 2.25rem;
font-weight: 600;
margin: 0.75rem 0;
color: var(--codex-text-contrast);
}
h1 {
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-message {
margin-bottom: 0.75rem;
mix-blend-mode: difference;
h2 {
font-size: 1.875rem;
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 {
ChangeEvent,
ComponentType,
CSSProperties,
forwardRef,
InputHTMLAttributes,
useState,
@ -9,16 +8,6 @@ import {
import { attributes } from "../utils/attributes";
import { classnames } from "../utils/classnames";
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 = {
id: string;
@ -80,54 +69,39 @@ export const Input = forwardRef<HTMLInputElement, Props>(
};
return (
<>
{label && (
<label className="input-label" htmlFor={id}>
{label}
</label>
<div
className={classnames(
["input"],
["input--invalid", invalid || isInvalid],
["input--icon", !!Icon],
[inputClassName || ""]
)}
>
{label && <label htmlFor={id}>{label}</label>}
<div
className={classnames(
["input-icon", !!Icon],
[inputContainerClassName]
)}
>
<div className={classnames([inputContainerClassName])}>
{Icon && (
<div className="input-iconElement">
<div>
<Icon />
</div>
)}
<input
id={id}
ref={ref}
className={classnames(
["input"],
["input--invalid", invalid || isInvalid],
["input-icon-input", !!Icon],
[inputClassName || ""]
)}
className={classnames([inputClassName || ""])}
onChange={onInternalChange}
style={style}
{...attributes({
disabled,
"aria-disabled": disabled,
"aria-invalid": invalid || isInvalid,
})}
{...rest}
/>
</div>
{helper && (
<div>
<SimpleText
className="input-helper-text"
variant={invalid || isInvalid ? "error" : "light"}
>
{helper}
</SimpleText>
</div>
)}
</>
{helper && <small>{helper}</small>}
</div>
);
}
);

View File

@ -1,105 +1,71 @@
.input {
background-color: var(--codex-input-background);
color: white;
border-radius: var(--codex-border-radius);
border: 1px solid var(--codex-input-border-color);
padding: 16px;
outline: none;
display: inline-block;
box-sizing: border-box;
font-weight: 500;
font-size: 20px;
height: 64px;
}
.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 {
background-color: var(--codex-input-background);
color: white;
border-radius: var(--codex-border-radius);
border: 1px solid var(--codex-input-border-color);
padding: 16px;
outline: none;
display: inline-block;
box-sizing: border-box;
font-weight: 500;
font-size: 20px;
height: 64px;
position: relative;
}
} */
&.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 { Input } from "../Input/Input";
import { Select } from "../Select/Select";
export interface CustomStyleCSS extends CSSProperties {
"--codex-border-radius"?: string;
"--codex-border-color"?: string;
}
type Props = {
label: string;
@ -63,13 +58,6 @@ type Props = {
step?: string;
/**
* Apply custom css variables.
* --codex-border-radius
* --codex-border-color
*/
style?: CustomStyleCSS;
name?: string;
/**
@ -91,7 +79,6 @@ export function InputGroup({
name,
helper,
type = "text",
style,
group,
className = "",
inputClassName = "",
@ -111,17 +98,17 @@ export function InputGroup({
extra,
}: Props) {
return (
<div className={`inputGroup ${className}`} style={style}>
<div className="inputGroup-container">
<div className="inputGroup-element">
<div className="inputGroup-inputContainer">
<div className={`inputGroup input-group ${className}`}>
<div>
<div>
<div>
<Input
ref={inputRef}
id={id}
name={name}
label={label}
onChange={onChange}
inputClassName={"inputGroup-input " + inputClassName}
inputClassName={inputClassName}
type={type}
value={value}
step={step}
@ -149,12 +136,10 @@ export function InputGroup({
)}
</div>
</div>
<div className="inputGroup-helpers">
<span>
{helper && <small className="inputGroup-helper">{helper}</small>}
</span>
<p>
<span>{helper && <small>{helper}</small>}</span>
{extra}
</div>
</p>
</div>
</div>
);

View File

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

View File

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

View File

@ -1,64 +1,55 @@
.modal {
transition:
transform 0.25s,
opacity 0.25s;
max-width: 800px;
overflow-y: auto;
overflow-x: hidden;
opacity: 0;
z-index: -1;
max-height: 100%;
left: 50%;
top: 50%;
transform: translateX(-50%);
position: fixed;
display: flex;
flex-direction: column;
background-color: var(--codex-background);
padding: 1.5rem;
border-radius: var(--codex-border-radius);
}
.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 {
dialog {
transition:
transform 0.25s,
opacity 0.25s;
max-width: 800px;
overflow-y: auto;
overflow-x: hidden;
opacity: 0;
z-index: -1;
max-height: 100%;
left: 50%;
top: 50%;
transform: translateX(-50%);
position: fixed;
display: flex;
flex-direction: column;
background-color: var(--codex-background);
padding: 1.5rem;
border-radius: var(--codex-border-radius);
border: none;
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 { Button } from "../Button/Button";
import "./placeholder.css";
import { SimpleText } from "../SimpleText/SimpleText";
type Props = {
title: string;
@ -35,14 +34,10 @@ export function Placeholder({
}: Props) {
return (
<div className={"placeholder " + className}>
<div className="placeholder-icon">{Icon}</div>
<b className="placeholder-title">{title}</b>
<div>{Icon}</div>
<b>{title}</b>
{subtitle && (
<div className="placeholder-subtitle">
<SimpleText variant="light">{subtitle}</SimpleText>
</div>
)}
{subtitle && <p>subtitle</p>}
<div className="placeholder-message">{message} </div>

View File

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

View File

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

View File

@ -1,44 +1,46 @@
.select {
appearance: none;
-moz-appearance: none;
-webkit-appearance: none;
background-color: var(
--codex-select-background,
var(--codex-background-secondary)
);
outline: 2px solid transparent;
outline-offset: 2px;
color: var(--codex-color);
border-radius: var(--codex-border-radius);
padding: 0.75rem 1rem;
padding-inline-end: 2.25rem;
transition: box-shadow 0.35s;
border: var(--codex-select-border, 1px solid var(--codex-border-color));
background-image: var(
--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-size: 1.25em 1.25em;
box-sizing: border-box;
}
select {
appearance: none;
-moz-appearance: none;
-webkit-appearance: none;
background-color: var(
--codex-select-background,
var(--codex-background-secondary)
);
outline: 2px solid transparent;
outline-offset: 2px;
color: var(--codex-color);
border-radius: var(--codex-border-radius);
padding: 0.75rem 1rem;
padding-inline-end: 2.25rem;
transition: box-shadow 0.35s;
border: var(--codex-select-border, 1px solid var(--codex-border-color));
background-image: var(
--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-size: 1.25em 1.25em;
box-sizing: border-box;
.select:hover,
.select:focus-visible,
.select:active {
box-shadow: 0 0 0 1px var(--codex-border-color);
}
@media (min-width: 801px) {
& {
min-width: 20rem;
}
}
.select-label {
margin-bottom: 0.5rem;
font-weight: 500;
display: block;
color: var(--codex-color);
}
&:hover,
&:focus-visible,
&:active {
box-shadow: 0 0 0 1px var(--codex-border-color);
}
}
@media (min-width: 801px) {
.select {
min-width: 20rem;
label {
margin-bottom: 0.5rem;
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 });
return (
<div
className={classnames(
["sheets-container"],
["sheets-container--open", open]
)}
>
<Backdrop onClose={onClose} open={open} className={"sheets-backdrop"} />
<div className={classnames(["sheets"], ["sheets--open", open])}>
<Backdrop onClose={onClose} open={open} />
<div className="sheets" {...attr}>
{children}
</div>
<aside {...attr}>{children}</aside>
</div>
);
}

View File

@ -1,57 +1,53 @@
.sheets {
position: fixed;
transition: transform 0.25s;
background-color: var(--codex-background-secondary);
z-index: 2;
justify-content: space-between;
}
.sheets-container {
overflow: hidden;
width: 100%;
height: 100%;
left: 0;
top: 0;
z-index: -1;
}
.sheets-container--open {
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--open {
z-index: 2;
}
.sheets[aria-expanded] {
transform: translatex(0);
z-index: 10;
}
}
@media (max-width: 999px) {
.sheets {
width: 100%;
height: auto;
bottom: 0;
top: auto;
transform: translatey(1300px);
left: 0;
padding-bottom: 1.5rem;
}
.sheets[aria-expanded] {
transform: translatey(0);
z-index: 10;
aside {
position: fixed;
transition: transform 0.25s;
background-color: var(--codex-background-secondary);
z-index: 2;
justify-content: space-between;
@media (min-width: 1000px) {
& {
width: 300px;
height: 100%;
bottom: 0;
top: 0;
transform: translatex(1300px);
right: 0;
}
&[aria-expanded] {
transform: translatex(0);
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 "./spaceAllocation.css";
@ -25,36 +24,31 @@ export function SpaceAllocation({ data }: Props) {
const total = data.reduce((acc, val) => acc + val.size, 0);
return (
<>
<div className="nodeSpaceAllocation-bar">
<div className="space-allocation">
<header>
{data.map((d) => (
<span
key={d.title}
className={`nodeSpaceAllocation-barItem nodeSpaceAllocation-barQuota ${d.className || ""}`}
className={`${d.className || ""}`}
style={{
width: (d.size / total) * 100 + "%",
backgroundColor: d.color,
}}
></span>
))}
</div>
</header>
<div className="nodeSpaceAllocation-legend">
<ul className="nodeSpaceAllocation-legend">
{data.map((d) => (
<div key={d.title} className={"nodeSpaceAllocation-legendRow"}>
<div
className={`nodeSpaceAllocation-legendItem nodeSpaceAllocation-quota`}
style={{ backgroundColor: d.color }}
></div>
<div className="nodeSpaceAllocation-legendItem-text">
<small>{d.title}</small>
<SimpleText variant="light" size="small">
{PrettyBytes(d.size)}
</SimpleText>
</div>
</div>
<li key={d.title}>
<span style={{ backgroundColor: d.color }}></span>
<p>
<span>{d.title}</span>
<small> {PrettyBytes(d.size)}</small>
</p>
</li>
))}
</div>
</>
</ul>
</div>
);
}

View File

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

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

View File

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

View File

@ -3,120 +3,46 @@
flex-direction: column;
background-color: var(--codex-background);
border-radius: var(--codex-border-radius);
}
.stepper-progress,
.stepper-step-info,
.stepper-steps,
.stepper-step {
display: flex;
align-items: center;
gap: 0.5rem;
transition: opacity 0.35s;
}
header {
display: flex;
align-items: center;
gap: 0.5rem;
transition: opacity 0.35s;
}
.stepper-step:not([disabled]):not(.stepper-separator-active):hover {
cursor: pointer;
opacity: 0.8;
}
main {
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-number {
width: 1.75rem;
height: 1.75rem;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: background-color 0.35s;
}
@media (min-width: 801px) {
& {
min-width: 500px;
}
}
}
.stepper-number:not(.stepper-number-active):not(.stepper-number-done) {
background-color: var(
--codex-stepper-background,
var(--codex-background-light)
);
}
&.stepper--progress {
main {
justify-content: center;
flex: 1;
align-items: center;
}
}
.stepper-separator {
height: 1px;
flex: 1;
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;
footer {
display: flex;
justify-content: space-between;
}
}
@keyframes step {
@ -138,40 +64,3 @@
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 { ArrowDownUp, Search } from "lucide-react";
import { Row, RowProps } from "./Row";
import { Fragment, ReactElement, useEffect, useState } from "react";
import { Fragment, ReactElement, ReactNode, useEffect, useState } from "react";
import { classnames } from "../utils/classnames";
import { attributes } from "../utils/attributes";
export type TabSortState = "asc" | "desc" | null;
@ -61,10 +61,16 @@ export function Table({
};
return (
<div className={`table-container ${className}`}>
<table className={"table"}>
<thead className="table-thead">
<tr className="table-theadTr">
<div
className={classnames(
["table"],
[className],
["table--empty", !!rows.length]
)}
>
<table>
<thead>
<tr>
{headers.map((col, index) => {
const [name, sort] = Array.isArray(col) ? col : [col];
const state = index === sortSelected[0] ? sortSelected[1] : null;
@ -72,9 +78,14 @@ export function Table({
return (
<th
className={classnames(
["table-theadTh"],
["table-theadTh--clickable", !!sort]
{...attributes(
sort
? {
role: "button",
"aria-sort":
state === "asc" ? "ascending" : "descending",
}
: {}
)}
key={name}
onClick={() => {
@ -82,14 +93,9 @@ export function Table({
sort?.(nxt);
}}
>
<div className="table-theadTh-content">
<div>
<span>{name}</span>
{sort && (
<ArrowDownUp
className={"table-theadTh-icon--" + state}
size={"1rem"}
></ArrowDownUp>
)}
{sort && <ArrowDownUp size={"1rem"}></ArrowDownUp>}
</div>
</th>
);
@ -104,11 +110,39 @@ export function Table({
</table>
{!rows.length && (
<div className="table-placeholder">
<div>
<Search />
<p className="table-placeholderText">No data.</p>
<p>No data.</p>
</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 {
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;
border-radius: var(--codex-border-radius);
overflow-x: auto;
overflow-y: hidden;
}
.table-theadTr {
border-bottom: 1px solid var(--codex-border-color);
}
table {
border-spacing: 0 12px;
width: 100%;
.table-theadTh {
color: var(--codex-color-light);
font-weight: normal;
text-transform: uppercase;
font-size: 0.9rem;
text-align: left;
padding: 1rem;
}
thead {
tr {
border-bottom: 1px solid var(--codex-border-color);
border-radius: 8px;
.table-theadTh--clickable {
cursor: pointer;
}
th {
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 {
display: flex;
align-items: center;
gap: 0.5rem;
}
&[role="button"] {
cursor: pointer;
}
.table-theadTh-content svg {
position: relative;
top: -2px;
cursor: pointer;
}
div {
display: flex;
align-items: center;
gap: 0.5rem;
}
.table-theadTh-content svg path {
opacity: 0.5;
transition: opacity 0.35s;
}
svg {
position: relative;
top: -2px;
cursor: pointer;
}
.table-theadTh-icon--desc path:nth-child(1),
.table-theadTh-icon--desc path:nth-child(2) {
opacity: 1;
}
svg path {
opacity: 0.5;
transition: opacity 0.35s;
}
.table-theadTh-icon--asc path:nth-child(3),
.table-theadTh-icon--asc path:nth-child(4) {
opacity: 1;
th[aria-sort="descending"] path:nth-child(1),
th[aria-sort="descending"] path:nth-child(2) {
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 "./tabs.css";
import { classnames } from "../utils/classnames";
import { attributes } from "../utils/attributes";
export type TabProps = {
label: string;
@ -26,11 +27,8 @@ export function Tabs({ tabs, onTabChange, tabIndex }: Props) {
{tabs.map((tab, index) => (
<div
key={tab.label}
className={classnames(
["tabs-tab"],
["tabs-tab--active", tabIndex === index],
[tab.className || ""]
)}
{...attributes({ "aria-selected": tabIndex === index })}
className={classnames([tab.className || ""])}
onClick={() => onTabChange(index)}
>
{tab.Icon && <tab.Icon />}

View File

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

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 "./toast.css";
import { CircleCheck, CircleX, Info, X } from "lucide-react";
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 = {
message: string;
@ -30,22 +23,12 @@ type Props = {
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";
};
export function Toast({
message,
time,
style,
variant,
className = "",
duration = 3000,
@ -84,12 +67,11 @@ export function Toast({
<div
className={`toast ${className} toast--${variant}`}
{...attributes({ "aria-hidden": time == 0 || msg === "" })}
style={style}
>
<Icon size="1.25rem" className="toast-icon" />
<Icon size="1.25rem" />
<span>
<b className="toast-title">{variant} ! </b>
<b>{variant} ! </b>
<span>{msg}</span>
</span>

View File

@ -18,33 +18,33 @@
border: 1px solid rgb(var(--codex-toast-color));
background: rgba(var(--codex-toast-color), 1);
z-index: 20;
}
.toast-close {
margin-left: 0.75rem;
}
&[aria-hidden] {
transform: translateX(1000px);
}
.toast[aria-hidden] {
transform: translateX(1000px);
}
&.toast--success {
--codex-toast-color: var(--codex-color-success);
}
.toast-title {
text-transform: capitalize;
}
&.toast--error {
--codex-toast-color: var(--codex-color-error);
}
.toast-icon {
fill: rgba(var(--codex-toast-color), 0.2);
stroke: white;
}
&.toast--default {
--codex-toast-color: var(--codex-color-grey);
}
.toast--success {
--codex-toast-color: var(--codex-color-success);
}
.button {
margin-left: 0.75rem;
}
.toast--error {
--codex-toast-color: var(--codex-color-error);
}
b {
text-transform: capitalize;
}
.toast--default {
--codex-toast-color: var(--codex-color-grey);
svg {
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 { ChangeEvent, CSSProperties, DragEventHandler, useRef } from "react";
import { ChangeEvent, DragEventHandler, useRef } from "react";
import { attributes } from "../utils/attributes.ts";
import "./upload.css";
import { UploadFile } from "./UploadFile.tsx";
@ -7,17 +7,6 @@ import { useUploadStategy } from "./useUploadStrategy.ts";
import { classnames } from "../utils/classnames.ts";
import { ButtonIcon } from "../ButtonIcon/ButtonIcon.tsx";
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 = {
/**
@ -68,18 +57,6 @@ type Props = {
*/
// 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.
* Default: File uploaded successfully.
@ -151,17 +128,16 @@ export function Upload({
onMouseLeave={onMouseLeave}
>
<ButtonIcon Icon={multiple ? FileStack : UploadIcon}></ButtonIcon>
<div className="upload-text">
<div>
<b>
Drop your {multiple ? "file(s)" : "file"} here or{" "}
<span className="text--primary">browse</span>
</b>
</div>
<SimpleText size="small" variant="light" center>
{multiple ? "Up to 10 files" : "Choose one single file"}
</SimpleText>
</div>
<p>
<b>
Drop your {multiple ? "file(s)" : "file"} here or{" "}
<span>browse</span>
</b>
</p>
<small> {multiple ? "Up to 10 files" : "Choose one single file"}</small>
<input
data-testid="upload"
type="file"
@ -171,7 +147,7 @@ export function Upload({
{...attributes({ multiple: multiple })}
/>
{warning && <SimpleText variant="warning">{warning}</SimpleText>}
{warning && <span>{warning}</span>}
</div>
{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 { WebFileIcon } from "../WebFileIcon/WebFileIcon";
import { ButtonIcon } from "../ButtonIcon/ButtonIcon";
import { SimpleText } from "../SimpleText/SimpleText";
import "./UploadFile.css";
type UploadFileProps = {
file: File;
@ -263,37 +263,32 @@ export function UploadFile({
const ActionIcon = () => <UploadActionIcon status={status} />;
return (
<>
<div className={"uploadFile"}>
<div className="uploadFile-info">
<div className="uploadFile-infoLeft">
<div
className="upload-file"
{...attributes({
"aria-invalid": status === "error",
"data-done": status === "done",
})}
>
<div>
<header>
<div>
{preview ? (
<img
src={preview}
width="24"
alt="Preview"
className="uploadFile-preview"
/>
<img src={preview} width="24" alt="Preview" />
) : (
<WebFileIcon type={file.type} />
)}
<div className="uploadFile-infoText">
<b
className="uploadFile-name"
{...attributes({
"aria-invalid": status === "error",
"data-done": status === "done",
})}
>
<span className="uploadFile-filename">{filename}</span>
<p>
<b>
<span>{filename}</span>
{extension && <span>.{extension}</span>}
</b>
<div>
<small>{PrettyBytes(file.size)}</small>
</div>
</div>
</p>
</div>
<div className="uploadFile-infoRight">
<div>
<UploadStatusIcon status={status} />
<ButtonIcon
@ -302,33 +297,27 @@ export function UploadFile({
Icon={ActionIcon}
></ButtonIcon>
</div>
</div>
</header>
<div className="uploadFile-progress">
<main>
<progress
className="uploadFile-progressBar"
{...attributes({
max: file ? progress.total.toString() : false,
value: file ? progress.loaded.toString() : false,
"aria-invalid": status === "error",
})}
/>
<span className="uploadFile-progressBarPercent">
{percent.toFixed(2)} %
</span>
</div>
<span>{percent.toFixed(2)} %</span>
</main>
<div className="uploadFile-message">
<footer>
{cid ? (
<div className="text--primary">{successMessage}</div>
<span>{successMessage}</span>
) : (
<SimpleText variant="error">
{error ? error : <>&nbsp;</>}
</SimpleText>
<span> {error ? error : <>&nbsp;</>}</span>
)}
</div>
</footer>
</div>
</>
</div>
);
}

View File

@ -11,172 +11,24 @@
gap: 0.5rem;
cursor: pointer;
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;
align-items: center;
}
.uploadFile-name[aria-invalid] {
color: rgb(var(--codex-color-error));
}
p {
margin-bottom: 0;
margin-top: 12px;
line-height: 4px;
.uploadFile-name[data-done] {
color: var(--codex-color-primary);
}
span {
color: var(--codex-color-primary);
}
}
.uploadFile-infoText {
flex-grow: 1;
}
small {
color: var(--codex-input-label-color);
}
.uploadFile-infoRight {
justify-content: space-around;
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;
input + span {
color: var(--codex-color-warning);
}
}

View File

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

View File

@ -65,7 +65,12 @@ const ActionTemplate = (props: Props) => {
return (
<div style={{ padding: "6rem" }}>
<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>
</Modal>
</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",
size: 10000000,
color: "red"
},
{
title: "New space allocation",
size: 10000000 * 0.2,
color: "yellow"
},
{
title: "Remaining space",
size: 10000000 * 0.2,
color: "green"
},
],
},

View File

@ -1,7 +1,6 @@
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 { Row } from "../src/components/Table/Row";
import { Cell } from "../src";
const meta = {