Improvements for design integration

This commit is contained in:
Arnaud 2024-11-09 13:07:48 +07:00
parent 001d008a27
commit 3281ec5c9d
No known key found for this signature in database
GPG Key ID: 69D6CE281FCAE663
19 changed files with 300 additions and 191 deletions

4
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.40", "version": "0.0.42",
"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.40", "version": "0.0.42",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"lucide-react": "^0.453.0" "lucide-react": "^0.453.0"

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.40", "version": "0.0.41",
"type": "module", "type": "module",
"scripts": { "scripts": {
"prepack": "npm run build", "prepack": "npm run build",

View File

@ -0,0 +1,12 @@
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10.0001 8.93955L13.7126 5.22705L14.7731 6.28755L11.0606 10.0001L14.7731 13.7126L13.7126 14.7731L10.0001 11.0606L6.28755 14.7731L5.22705 13.7126L8.93955 10.0001L5.22705 6.28755L6.28755 5.22705L10.0001 8.93955Z"
fill="white"
/>
</svg>

After

Width:  |  Height:  |  Size: 362 B

12
src/assets/icons/next.svg Normal file
View File

@ -0,0 +1,12 @@
<svg
width="21"
height="20"
viewBox="0 0 21 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.2958 9.99907L7.58325 6.28657L8.64375 5.22607L13.4168 9.99907L8.64375 14.7721L7.58325 13.7116L11.2958 9.99907Z"
fill="black"
/>
</svg>

After

Width:  |  Height:  |  Size: 266 B

View File

@ -0,0 +1,12 @@
<svg
width="21"
height="20"
viewBox="0 0 21 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.70425 9.99907L13.4168 13.7116L12.3563 14.7721L7.58325 9.99907L12.3563 5.22607L13.4168 6.28657L9.70425 9.99907Z"
fill="#969696"
/>
</svg>

After

Width:  |  Height:  |  Size: 268 B

View File

@ -0,0 +1,16 @@
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0 10C0 4.47715 4.47715 0 10 0C15.5228 0 20 4.47715 20 10C20 15.5228 15.5228 20 10 20C4.47715 20 0 15.5228 0 10Z"
fill="#1FC16B"
/>
<path
d="M15.0999 7.45336L8.72547 13.8202L4.8999 9.99973L6.17479 8.72654L8.72547 11.2738L13.825 6.18018L15.0999 7.45336Z"
fill="white"
/>
</svg>

After

Width:  |  Height:  |  Size: 417 B

View File

@ -33,6 +33,8 @@ type Props = {
*/ */
Icon?: ComponentType; Icon?: ComponentType;
IconAfter?: ComponentType;
/** /**
* Apply custom classname. * Apply custom classname.
*/ */
@ -45,6 +47,7 @@ export function Button({
label, label,
className = "", className = "",
Icon, Icon,
IconAfter,
onMouseEnter, onMouseEnter,
onMouseLeave, onMouseLeave,
size = "medium", size = "medium",
@ -65,12 +68,9 @@ export function Button({
"aria-busy": fetching, "aria-busy": fetching,
})} })}
> >
{Icon && ( {Icon && <Icon />}
<div>
<Icon />
</div>
)}
<span>{label}</span> <span>{label}</span>
{IconAfter && <IconAfter />}
</button> </button>
); );
} }

View File

@ -13,7 +13,7 @@
padding: var(--codex-button-padding); padding: var(--codex-button-padding);
display: flex; display: flex;
place-items: center; place-items: center;
gap: 8px; gap: 2px;
font-weight: 500; font-weight: 500;
position: relative; position: relative;
border: none; border: none;
@ -61,8 +61,7 @@
&.button--primary:not(:disabled):hover { &.button--primary:not(:disabled):hover {
cursor: pointer; cursor: pointer;
box-shadow: 0 0 0 3px box-shadow: 0 0 0 3px var(--codex-button-color-box-shadow, #6fcb94cc);
var(--codex-button-color-box-shadow, var(--codex-color-primary-variant));
} }
&.button--outline:not(:disabled):hover { &.button--outline:not(:disabled):hover {
@ -71,7 +70,7 @@
} }
&.button--primary { &.button--primary {
background-color: var(--codex-color-primary); background-color: #6fcb94;
color: var(--codex-color-on-primary); color: var(--codex-color-on-primary);
} }

View File

@ -66,6 +66,8 @@ type Props = {
label: string; label: string;
id: string; id: string;
size?: "big" | "medium";
}; };
export function Dropdown({ export function Dropdown({
@ -81,6 +83,7 @@ export function Dropdown({
onSelected, onSelected,
value = "", value = "",
className = "", className = "",
size = "big",
}: Props) { }: Props) {
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const lower = value.toLocaleLowerCase(); const lower = value.toLocaleLowerCase();
@ -119,12 +122,13 @@ export function Dropdown({
return ( return (
<div className={"dropdown " + className}> <div className={"dropdown " + className}>
<label htmlFor={id}>{label}</label> {label && <label htmlFor={id}>{label}</label>}
<div> <div>
<Backdrop onClose={onClose} open={focused} /> <Backdrop onClose={onClose} open={focused} />
<Input <Input
autoComplete="off"
ref={inputRef} ref={inputRef}
onChange={onChange} onChange={onChange}
onFocus={onInternalFocus} onFocus={onInternalFocus}
@ -136,12 +140,13 @@ export function Dropdown({
value={value} value={value}
label={""} label={""}
id={id} id={id}
size={size as any}
/> />
<ul {...attr}> <ul {...attr}>
{filtered.length ? ( {filtered.length ? (
filtered.map((o) => ( filtered.map((o) => (
<li onClick={() => onSelect(o)} key={o.title}> <li onClick={() => onSelect(o)} key={o.title + o.subtitle}>
{o.Icon && <o.Icon />} {o.Icon && <o.Icon />}
<span>{o.title}</span> <span>{o.title}</span>
{o.subtitle && <span>{o.subtitle}</span>} {o.subtitle && <span>{o.subtitle}</span>}

View File

@ -71,11 +71,8 @@
background-position: right 0.5rem center; background-position: right 0.5rem center;
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: 1.25em 1.25em; background-size: 1.25em 1.25em;
&:focus {
z-index: 11; z-index: 11;
} }
}
p { p {
padding: 0.75rem 0.25rem; padding: 0.75rem 0.25rem;

View File

@ -1,8 +1,10 @@
import { ReactNode, useEffect, useState } from "react"; import { ComponentType, ReactNode, useEffect, useState } from "react";
import { Backdrop } from "../Backdrop/Backdrop"; import { Backdrop } from "../Backdrop/Backdrop";
import { Button } from "../Button/Button"; import { Button } from "../Button/Button";
import { classnames } from "../utils/classnames"; import { classnames } from "../utils/classnames";
import "./modal.css"; import "./modal.css";
import CloseIcon from "../../assets/icons/close.svg?react";
import { ButtonIcon } from "../ButtonIcon/ButtonIcon";
type Props = { type Props = {
open: boolean; open: boolean;
@ -55,6 +57,10 @@ type Props = {
children: ReactNode; children: ReactNode;
className?: string; className?: string;
title?: string;
Icon?: ComponentType<{ width: number | string | undefined }>;
}; };
export function Modal({ export function Modal({
@ -68,6 +74,8 @@ export function Modal({
labelActionButton = "Action", labelActionButton = "Action",
labelCloseButton = "Close", labelCloseButton = "Close",
children, children,
title,
Icon,
onAction, onAction,
}: Props) { }: Props) {
const [internalOpen, setInternalOpen] = useState(open); const [internalOpen, setInternalOpen] = useState(open);
@ -94,8 +102,24 @@ export function Modal({
<Backdrop open={internalOpen} onClose={internalClose} /> <Backdrop open={internalOpen} onClose={internalClose} />
<dialog> <dialog>
{title && (
<header>
<div>
{Icon && <Icon width={24}></Icon>}
<h6>{title}</h6>
</div>
<ButtonIcon
onClick={internalClose}
Icon={CloseIcon}
variant="small"
></ButtonIcon>
</header>
)}
<main>{open && children}</main> <main>{open && children}</main>
{displayCloseButton ||
(displayActionButton && (
<footer> <footer>
{displayCloseButton && ( {displayCloseButton && (
<Button <Button
@ -114,6 +138,7 @@ export function Modal({
/> />
)} )}
</footer> </footer>
))}
</dialog> </dialog>
</div> </div>
); );

View File

@ -3,7 +3,6 @@
transition: transition:
transform 0.25s, transform 0.25s,
opacity 0.25s; opacity 0.25s;
max-width: 800px;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
opacity: 0; opacity: 0;
@ -15,16 +14,54 @@
position: fixed; position: fixed;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background-color: var(--codex-background); background: #232323;
padding: 1.5rem; border-radius: 16px;
border-radius: var(--codex-border-radius);
border: none; border: none;
width: calc(100% - 6rem); width: calc(100% - 6rem);
@media (min-width: 801px) {
& { & {
min-width: 500px; min-width: 500px;
} }
> header {
padding: 16px;
display: flex;
align-items: center;
justify-content: space-between;
> div {
display: flex;
gap: 8px;
align-items: center;
}
.button-icon {
background-color: transparent;
border: 1px solid #96969633;
}
svg {
min-width: 20px;
}
h6 {
margin: 0;
font-family: Inter;
font-size: 16px;
font-weight: 500;
line-height: 24px;
letter-spacing: -0.011em;
}
}
> main {
flex: 1;
}
> footer {
margin-top: 1rem;
display: flex;
justify-content: center;
} }
} }
@ -37,16 +74,6 @@
z-index: 10; z-index: 10;
} }
main {
flex: 1;
}
footer {
margin-top: 1rem;
display: flex;
justify-content: center;
}
&.modal--actions footer { &.modal--actions footer {
margin-top: 1rem; margin-top: 1rem;
display: flex; display: flex;

View File

@ -1,119 +1,124 @@
.step { .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) { &:not(:last-child) {
flex: 1; flex: 1;
} }
--codex-step-background-color: white;
--codex-step-border-color: #e1e4ea;
--codex-step-label-color: #969696;
--codex-step-small-color: #96969699;
--codex-step-hr-color: #96969699;
--codex-step-animation: step-back;
&.step--done {
--codex-step-background-color: transparent;
--codex-step-border-color: transparent;
--codex-step-small-color: #1fc16b99;
--codex-step-label-color: #1fc16b;
--codex-step-hr-color: #1fc16b;
--codex-step-animation: step;
} }
> div:first-child { > div:first-child {
display: flex;
align-items: center;
gap: 0.5rem;
transition: opacity 0.35s;
width: 1.75rem;
height: 1.75rem;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border-radius: 50%; gap: 16px;
transition: background-color 0.35s; transition:
background-color: var( box-shadow 0.35s,
--codex-stepper-background, background-color 0.35s;
var(--codex-background-light) flex: 1;
); height: 20px;
position: relative;
span { span {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
mix-blend-mode: difference; background-color: var(--codex-step-background-color);
} border: 1px solid var(--codex-step-border-color);
} border-radius: 50%;
width: 20px;
height: 20px;
color: #525866;
font-family: Inter;
font-size: 12px;
font-weight: 500;
line-height: 16px;
text-align: center;
cursor: pointer;
transition: box-shadow 0.35s;
&.step--active > div:first-child, &:hover {
&.step--done > div:first-child { box-shadow: 0 0 0 2px var(--codex-border-color);
background-color: var(--codex-color-primary); }
} }
div:nth-child(2) {
display: flex;
flex: 1;
position: relative;
place-items: center;
hr { hr {
border: 0; border: 0;
height: 1px; height: 4px;
flex: 1; flex: 1;
background-color: var( background-color: #969696;
--codex-stepper-background, border-radius: 40px;
var(--codex-background-light)
);
position: relative; position: relative;
margin-bottom: 8px; margin-bottom: 8px;
@media (max-width: 800px) { &::before {
& { background-color: var(--codex-step-hr-color);
display: none; height: 4px;
content: " ";
position: absolute;
z-index: 1;
animation-duration: 1s;
animation-name: var(--codex-step-animation);
animation-fill-mode: forwards;
border-radius: 40px;
} }
} }
} }
&::before { > div:nth-child(2) {
background-color: var(--codex-color-primary); display: block;
height: 1px; padding-left: 38px;
content: " ";
position: absolute; small {
top: 8px; font-family: Inter;
animation-duration: 1s; font-size: 8px;
animation-name: step-back; font-weight: 700;
animation-fill-mode: forwards; line-height: 8px;
opacity: 0; text-align: left;
/* animation-direction: reverse; */ color: var(--codex-step-small-color);
display: block;
} }
span { span {
position: absolute; font-family: Inter;
top: 10px;
font-size: 14px; font-size: 14px;
font-weight: 400;
@media (max-width: 800px) { line-height: 20px;
& { letter-spacing: -0.006em;
display: none; text-align: left;
} color: var(--codex-step-label-color);
}
}
}
&.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 { @keyframes step-back {
div:nth-child(2) { 0% {
&::before { width: 100%;
opacity: 1; }
100% {
width: 0%;
} }
} }
@keyframes step {
0% {
width: 0;
}
100% {
width: 100%;
} }
} }

View File

@ -1,8 +1,8 @@
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"; import "./Step.css";
import ValidIcon from "../../assets/icons/valid.svg?react";
type StepProps = { type StepProps = {
/** /**
@ -34,6 +34,8 @@ type StepProps = {
* Event triggered when a step number is clicked on * Event triggered when a step number is clicked on
*/ */
onClick?: (step: number) => void; onClick?: (step: number) => void;
index: number;
}; };
export function Step({ export function Step({
@ -42,6 +44,7 @@ export function Step({
isLast, isLast,
isDone, isDone,
title, title,
index,
onClick, onClick,
}: StepProps) { }: StepProps) {
const mounted = useRef(false); const mounted = useRef(false);
@ -62,12 +65,13 @@ export function Step({
{...attributes({ disabled: !onClick })} {...attributes({ disabled: !onClick })}
> >
<div> <div>
<span>{isDone ? <Check size={"1.25rem"} /> : step + 1}</span> <span>{isDone ? <ValidIcon /> : step + 1}</span>
<hr />
</div> </div>
{!isLast && ( {!isLast && (
<div> <div>
<hr /> <small>STEP {index}</small>
<span>{title}</span> <span>{title}</span>
</div> </div>
)} )}

View File

@ -5,6 +5,8 @@ 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"; import { classnames } from "../utils/classnames";
import PreviousIcon from "../../assets/icons/previous.svg?react";
import NextIcon from "../../assets/icons/next.svg?react";
type Props = { type Props = {
/** /**
@ -94,6 +96,7 @@ export function Stepper({
<header> <header>
{titles.map((title, index) => ( {titles.map((title, index) => (
<Step <Step
index={index + 1}
title={title} title={title}
step={index} step={index}
isActive={index === state.step} isActive={index === state.step}
@ -115,11 +118,13 @@ export function Stepper({
variant="outline" variant="outline"
onClick={() => onChangeStep(state.step - 1)} onClick={() => onChangeStep(state.step - 1)}
disabled={!state.isBackEnable} disabled={!state.isBackEnable}
Icon={PreviousIcon}
/> />
<Button <Button
label={nextLabel} label={nextLabel}
onClick={() => onChangeStep(state.step + 1)} onClick={() => onChangeStep(state.step + 1)}
disabled={!state.isNextEnable} disabled={!state.isNextEnable}
IconAfter={NextIcon}
/> />
</footer> </footer>
</div> </div>

View File

@ -1,35 +1,22 @@
.stepper { .stepper {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background-color: var(--codex-background); padding: 16px;
border-radius: var(--codex-border-radius);
header { > header {
display: flex; display: flex;
align-items: center; align-items: flex-start;
gap: 0.5rem; gap: 16px;
transition: opacity 0.35s; border-bottom: 1px solid #96969633;
padding-bottom: 16px;
} }
main { > 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; display: flex;
flex-direction: column; flex-direction: column;
min-height: 200px;
@media (min-width: 801px) {
& {
min-width: 500px; min-width: 500px;
} }
}
}
&.stepper--progress { &.stepper--progress {
main { main {
@ -42,25 +29,9 @@
footer { footer {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
}
}
@keyframes step { .button {
0% { width: 105px;
width: 0;
}
100% {
width: 100%;
} }
} }
@keyframes step-back {
0% {
width: 100%;
}
100% {
width: 0%;
}
} }

View File

@ -18,8 +18,8 @@
text-align: center; text-align: center;
font-size: 0.85rem; font-size: 0.85rem;
padding: 0.5rem; padding: 0.5rem;
bottom: 140%; bottom: 30px;
left: -140%; right: 0;
} }
@keyframes tooltip { @keyframes tooltip {

View File

@ -35,10 +35,32 @@ const Template = (props: Props) => {
setOpen(false); setOpen(false);
}; };
const Icon = () => (
<svg
width="20"
height="10"
viewBox="0 0 20 10"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clipPath="url(#clip0_326_785)">
<path
d="M14.1666 7.91667L9.99992 3.75L5.83325 7.91667H14.1666ZM14.1666 12.0833L9.99992 16.25L5.83325 12.0833H14.1666Z"
fill="#969696"
/>
</g>
<defs>
<clipPath id="clip0_326_785">
<rect width="20" height="10" fill="white" />
</clipPath>
</defs>
</svg>
);
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}> <Modal title="Title" Icon={Icon} onClose={onClose} open={open}>
<p>Hello world</p> <p>Hello world</p>
</Modal> </Modal>
</div> </div>

View File

@ -1,3 +0,0 @@
.stepper-padding {
padding: 1.5rem;
}