Create test design
This commit is contained in:
50
webui/src/common/components/Button/Button.jsx
Normal file
50
webui/src/common/components/Button/Button.jsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import React from "react";
|
||||
import cn from "classnames";
|
||||
import "./styles.sass";
|
||||
|
||||
/*
|
||||
Props:
|
||||
- variant: primary | subtle | danger
|
||||
- size: sm | md | lg
|
||||
- full: boolean (full width)
|
||||
- icon: ReactNode (left icon)
|
||||
- iconRight: ReactNode (right icon)
|
||||
- loading: boolean
|
||||
*/
|
||||
|
||||
export const Button = ({
|
||||
as: Component = "button",
|
||||
variant = "primary",
|
||||
size = "md",
|
||||
full = false,
|
||||
icon,
|
||||
iconRight,
|
||||
loading = false,
|
||||
disabled,
|
||||
className,
|
||||
children,
|
||||
...rest
|
||||
}) => {
|
||||
const isDisabled = disabled || loading;
|
||||
return (
|
||||
<Component
|
||||
className={cn(
|
||||
"btn",
|
||||
`btn--${variant}`,
|
||||
`btn--${size}`,
|
||||
full && "btn--full",
|
||||
loading && "is-loading",
|
||||
className
|
||||
)}
|
||||
disabled={isDisabled}
|
||||
{...rest}
|
||||
>
|
||||
{loading && <span className="btn__spinner" aria-hidden />}
|
||||
{icon && <span className="btn__icon btn__icon--left">{icon}</span>}
|
||||
<span className="btn__label">{children}</span>
|
||||
{iconRight && <span className="btn__icon btn__icon--right">{iconRight}</span>}
|
||||
</Component>
|
||||
);
|
||||
};
|
||||
|
||||
export default Button;
|
1
webui/src/common/components/Button/index.js
Normal file
1
webui/src/common/components/Button/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { Button as default } from "./Button.jsx";
|
84
webui/src/common/components/Button/styles.sass
Normal file
84
webui/src/common/components/Button/styles.sass
Normal file
@@ -0,0 +1,84 @@
|
||||
.btn
|
||||
--c-bg: #ffffff
|
||||
--c-bg-hover: #f2f5f8
|
||||
--c-bg-active: #e6ebf0
|
||||
--c-border: #d0d7de
|
||||
--c-border-hover: #c2cbd3
|
||||
--c-text: #1f2429
|
||||
--c-accent: #0f62fe
|
||||
--c-danger: #d93025
|
||||
position: relative
|
||||
display: inline-flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
gap: .5rem
|
||||
font-family: inherit
|
||||
font-weight: 500
|
||||
line-height: 1.2
|
||||
cursor: pointer
|
||||
border: 1px solid var(--c-border)
|
||||
background: var(--c-bg)
|
||||
color: var(--c-text)
|
||||
border-radius: 6px
|
||||
transition: background .15s ease, border-color .15s ease, color .15s ease, box-shadow .15s ease
|
||||
user-select: none
|
||||
text-decoration: none
|
||||
&:hover:not(:disabled)
|
||||
background: var(--c-bg-hover)
|
||||
border-color: var(--c-border-hover)
|
||||
&:active:not(:disabled)
|
||||
background: var(--c-bg-active)
|
||||
&:focus-visible
|
||||
outline: 2px solid var(--c-accent)
|
||||
outline-offset: 2px
|
||||
&:disabled
|
||||
opacity: .55
|
||||
cursor: not-allowed
|
||||
&.btn--full
|
||||
width: 100%
|
||||
&.btn--sm
|
||||
font-size: .75rem
|
||||
padding: .4rem .7rem
|
||||
&.btn--md
|
||||
font-size: .85rem
|
||||
padding: .6rem 1rem
|
||||
&.btn--lg
|
||||
font-size: 1rem
|
||||
padding: .8rem 1.2rem
|
||||
&.btn--primary
|
||||
--c-bg: #0f62fe
|
||||
--c-bg-hover: #0d55dd
|
||||
--c-bg-active: #0b47b8
|
||||
--c-border: #0f62fe
|
||||
--c-text: #ffffff
|
||||
&.btn--subtle
|
||||
--c-bg: #f3f6f9
|
||||
--c-bg-hover: #e8edf2
|
||||
--c-bg-active: #dfe5eb
|
||||
--c-border: #e1e6eb
|
||||
&.btn--danger
|
||||
--c-bg: #d93025
|
||||
--c-bg-hover: #c22b21
|
||||
--c-bg-active: #a9241b
|
||||
--c-border: #d93025
|
||||
--c-text: #ffffff
|
||||
|
||||
.btn__icon
|
||||
display: inline-flex
|
||||
align-items: center
|
||||
&--left
|
||||
margin-right: .25rem
|
||||
&--right
|
||||
margin-left: .25rem
|
||||
|
||||
.btn__spinner
|
||||
width: 14px
|
||||
height: 14px
|
||||
border: 2px solid rgba(0,0,0,.15)
|
||||
border-top-color: var(--c-text)
|
||||
border-radius: 50%
|
||||
animation: spin .7s linear infinite
|
||||
|
||||
@keyframes spin
|
||||
to
|
||||
transform: rotate(360deg)
|
27
webui/src/common/components/Input/Input.jsx
Normal file
27
webui/src/common/components/Input/Input.jsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React from "react";
|
||||
import cn from "classnames";
|
||||
import "./styles.sass";
|
||||
|
||||
/* Minimal input wrapper with label, error text, and optional icon */
|
||||
export const Input = ({
|
||||
label,
|
||||
error,
|
||||
icon,
|
||||
className,
|
||||
containerClassName,
|
||||
type = "text",
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<div className={cn("field", containerClassName)}>
|
||||
{label && <label className="field__label">{label}</label>}
|
||||
<div className={cn("field__control", error && "has-error", icon && "has-icon", className)}>
|
||||
{icon && <span className="field__icon">{icon}</span>}
|
||||
<input type={type} className="field__input" {...rest} />
|
||||
</div>
|
||||
{error && <div className="field__error">{error}</div>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Input;
|
1
webui/src/common/components/Input/index.js
Normal file
1
webui/src/common/components/Input/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { Input as default } from "./Input.jsx";
|
58
webui/src/common/components/Input/styles.sass
Normal file
58
webui/src/common/components/Input/styles.sass
Normal file
@@ -0,0 +1,58 @@
|
||||
.field
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: .35rem
|
||||
font-size: .8rem
|
||||
font-weight: 500
|
||||
color: #374048
|
||||
|
||||
.field__label
|
||||
letter-spacing: .5px
|
||||
|
||||
.field__control
|
||||
position: relative
|
||||
display: flex
|
||||
align-items: center
|
||||
background: #ffffff
|
||||
border: 1px solid #d0d7de
|
||||
border-radius: 6px
|
||||
padding: .55rem .7rem
|
||||
transition: border-color .15s ease, background .15s ease, box-shadow .15s ease
|
||||
&.has-icon .field__input
|
||||
padding-left: 1.6rem
|
||||
&.has-error
|
||||
border-color: #d93025
|
||||
box-shadow: 0 0 0 1px #d93025
|
||||
&:focus-within
|
||||
border-color: #0f62fe
|
||||
box-shadow: 0 0 0 1px #0f62fe20
|
||||
|
||||
.field__icon
|
||||
position: absolute
|
||||
left: .55rem
|
||||
top: 50%
|
||||
transform: translateY(-50%)
|
||||
display: inline-flex
|
||||
font-size: 1rem
|
||||
color: #6b7781
|
||||
pointer-events: none
|
||||
|
||||
.field__input
|
||||
appearance: none
|
||||
outline: none
|
||||
background: transparent
|
||||
border: 0
|
||||
color: #1f2429
|
||||
font: inherit
|
||||
width: 100%
|
||||
line-height: 1.2
|
||||
&::placeholder
|
||||
color: #a0abb4
|
||||
&:focus
|
||||
outline: none
|
||||
|
||||
.field__error
|
||||
font-size: .65rem
|
||||
font-weight: 600
|
||||
color: #d93025
|
||||
letter-spacing: .5px
|
Reference in New Issue
Block a user