ppc login works in mme main
This commit is contained in:
@@ -8,10 +8,9 @@ license = "MIT OR Apache-2.0"
|
||||
[dependencies]
|
||||
dioxus = { version = "0.7", default-features = false }
|
||||
dioxus-primitives = { git = "https://github.com/DioxusLabs/components", default-features = false }
|
||||
dioxus-code = { version = "0.1.0", default-features = false, features = ["macro", "lang-css"] }
|
||||
dioxus-icons = "0.1.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
web = ["dioxus/web", "dioxus-primitives/web"]
|
||||
desktop = ["dioxus/desktop"]
|
||||
desktop = ["dioxus/desktop", "dioxus-primitives/web"]
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
/* mize UI theme — include once; toggle via html[data-theme="light"|"dark"] */
|
||||
|
||||
html[data-theme="dark"] {
|
||||
--dark: initial;
|
||||
--light: ;
|
||||
}
|
||||
|
||||
html[data-theme="light"] {
|
||||
--dark: ;
|
||||
--light: initial;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme]) {
|
||||
--dark: initial;
|
||||
--light: ;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root:not([data-theme]) {
|
||||
--dark: ;
|
||||
--light: initial;
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg: var(--dark, #171d18) var(--light, #f7f8f4);
|
||||
--surface: var(--dark, #242d25) var(--light, #ffffff);
|
||||
--surface-2: var(--dark, #2c362d) var(--light, #eff2ea);
|
||||
--text: var(--dark, #f2f4ea) var(--light, #20251c);
|
||||
--text-2: var(--dark, #aab5a4) var(--light, #66705e);
|
||||
--border: var(--dark, #3a463a) var(--light, #dfe4d9);
|
||||
--accent: var(--dark, #d7e47d) var(--light, #fec251);
|
||||
--accent-text: var(--dark, #d7e47d) var(--light, #79500f);
|
||||
--success: var(--dark, #b9d987) var(--light, #526f32);
|
||||
--info: var(--dark, #b9e4eb) var(--light, #376777);
|
||||
--warning: var(--dark, #ffd77f) var(--light, #a66b13);
|
||||
--danger: var(--dark, #ff9b8e) var(--light, #b94d42);
|
||||
|
||||
/* On-accent fill text: dark ink on light accent, dark surface on dark accent */
|
||||
--on-accent: var(--dark, #171d18) var(--light, #79500f);
|
||||
--focus-ring: var(--accent);
|
||||
}
|
||||
@@ -3,9 +3,92 @@ use dioxus_primitives::dioxus_attributes::attributes;
|
||||
use dioxus_primitives::avatar::{self, AvatarState};
|
||||
use dioxus_primitives::merge_attributes;
|
||||
|
||||
#[css_module("/src/components/avatar/style.css")]
|
||||
struct Styles;
|
||||
|
||||
const STYLE: &str = r#".dx-avatar {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
overflow: hidden;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.dx-avatar-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
aspect-ratio: 1;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.dx-avatar-sm {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.dx-avatar-md {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.dx-avatar-lg {
|
||||
width: 3.5rem;
|
||||
height: 3.5rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.dx-avatar-circle {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.dx-avatar-rounded {
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
|
||||
.dx-avatar[data-state="loading"] {
|
||||
animation: dx-pulse 1.5s infinite ease-in-out;
|
||||
background: var(--surface-2);
|
||||
}
|
||||
|
||||
.dx-avatar[data-state="empty"] {
|
||||
background: var(--surface-2);
|
||||
}
|
||||
|
||||
@keyframes dx-pulse {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dx-avatar-fallback {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--surface-2);
|
||||
color: var(--text-2);
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.dx-avatar[data-state="error"] .dx-avatar-fallback {
|
||||
background: var(--surface-2);
|
||||
color: var(--text-2);
|
||||
}
|
||||
"#;
|
||||
#[derive(Clone, Copy, PartialEq, Default)]
|
||||
pub enum AvatarImageSize {
|
||||
#[default]
|
||||
@@ -17,9 +100,9 @@ pub enum AvatarImageSize {
|
||||
impl AvatarImageSize {
|
||||
fn to_class(self) -> &'static str {
|
||||
match self {
|
||||
AvatarImageSize::Small => Styles::dx_avatar_sm.inner,
|
||||
AvatarImageSize::Medium => Styles::dx_avatar_md.inner,
|
||||
AvatarImageSize::Large => Styles::dx_avatar_lg.inner,
|
||||
AvatarImageSize::Small => "dx-avatar-sm",
|
||||
AvatarImageSize::Medium => "dx-avatar-md",
|
||||
AvatarImageSize::Large => "dx-avatar-lg",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,8 +117,8 @@ pub enum AvatarShape {
|
||||
impl AvatarShape {
|
||||
fn to_class(self) -> &'static str {
|
||||
match self {
|
||||
AvatarShape::Circle => Styles::dx_avatar_circle.inner,
|
||||
AvatarShape::Rounded => Styles::dx_avatar_rounded.inner,
|
||||
AvatarShape::Circle => "dx-avatar-circle",
|
||||
AvatarShape::Rounded => "dx-avatar-rounded",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,7 +156,7 @@ pub struct AvatarProps {
|
||||
pub fn Avatar(props: AvatarProps) -> Element {
|
||||
let class = format!(
|
||||
"{} {} {}",
|
||||
Styles::dx_avatar,
|
||||
"dx-avatar",
|
||||
props.size.to_class(),
|
||||
props.shape.to_class()
|
||||
);
|
||||
@@ -83,6 +166,8 @@ pub fn Avatar(props: AvatarProps) -> Element {
|
||||
let merged = merge_attributes(vec![base, props.attributes]);
|
||||
|
||||
rsx! {
|
||||
crate::OnceStyle { href: "dioxus-components/avatar", css: STYLE }
|
||||
|
||||
avatar::Avatar {
|
||||
on_load: props.on_load,
|
||||
on_error: props.on_error,
|
||||
@@ -110,12 +195,14 @@ pub struct AvatarImageProps {
|
||||
#[component]
|
||||
pub fn AvatarImage(props: AvatarImageProps) -> Element {
|
||||
let base = attributes!(img {
|
||||
class: Styles::dx_avatar_image,
|
||||
class: "dx-avatar-image",
|
||||
draggable: "false",
|
||||
});
|
||||
let merged = merge_attributes(vec![base, props.attributes]);
|
||||
|
||||
rsx! {
|
||||
crate::OnceStyle { href: "dioxus-components/avatar", css: STYLE }
|
||||
|
||||
avatar::AvatarImage {
|
||||
id: props.id,
|
||||
src: props.src,
|
||||
@@ -136,11 +223,13 @@ pub struct AvatarFallbackProps {
|
||||
#[component]
|
||||
pub fn AvatarFallback(props: AvatarFallbackProps) -> Element {
|
||||
let base = attributes!(span {
|
||||
class: Styles::dx_avatar_fallback,
|
||||
class: "dx-avatar-fallback",
|
||||
});
|
||||
let merged = merge_attributes(vec![base, props.attributes]);
|
||||
|
||||
rsx! {
|
||||
crate::OnceStyle { href: "dioxus-components/avatar", css: STYLE }
|
||||
|
||||
avatar::AvatarFallback {
|
||||
attributes: merged,
|
||||
{props.children}
|
||||
@@ -187,6 +276,8 @@ pub struct ImageAvatarProps {
|
||||
#[component]
|
||||
pub fn ImageAvatar(props: ImageAvatarProps) -> Element {
|
||||
rsx! {
|
||||
crate::OnceStyle { href: "dioxus-components/avatar", css: STYLE }
|
||||
|
||||
Avatar {
|
||||
on_load: props.on_load,
|
||||
on_error: props.on_error,
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
.dx-avatar {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
overflow: hidden;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.dx-avatar-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
aspect-ratio: 1;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.dx-avatar-sm {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.dx-avatar-md {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.dx-avatar-lg {
|
||||
width: 3.5rem;
|
||||
height: 3.5rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.dx-avatar-circle {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.dx-avatar-rounded {
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
|
||||
.dx-avatar[data-state="loading"] {
|
||||
animation: dx-pulse 1.5s infinite ease-in-out;
|
||||
background: var(--surface-2);
|
||||
}
|
||||
|
||||
.dx-avatar[data-state="empty"] {
|
||||
background: var(--surface-2);
|
||||
}
|
||||
|
||||
@keyframes dx-pulse {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dx-avatar-fallback {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--surface-2);
|
||||
color: var(--text-2);
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.dx-avatar[data-state="error"] .dx-avatar-fallback {
|
||||
background: var(--surface-2);
|
||||
color: var(--text-2);
|
||||
}
|
||||
@@ -1,9 +1,45 @@
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_icons::lucide::BadgeCheck;
|
||||
|
||||
#[css_module("/src/components/badge/style.css")]
|
||||
struct Styles;
|
||||
|
||||
const STYLE: &str = r#".dx-badge {
|
||||
display: inline-flex;
|
||||
min-width: 20px;
|
||||
height: 20px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 9999px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
gap: 4px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.dx-badge[padding="true"] {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.dx-badge[data-style="primary"] {
|
||||
background-color: var(--accent);
|
||||
color: var(--on-accent);
|
||||
}
|
||||
|
||||
.dx-badge[data-style="secondary"] {
|
||||
background-color: var(--surface-2);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dx-badge[data-style="outline"] {
|
||||
border: 1px solid var(--border);
|
||||
background-color: var(--surface);
|
||||
color: var(--text-2);
|
||||
}
|
||||
|
||||
.dx-badge[data-style="destructive"] {
|
||||
background-color: var(--danger);
|
||||
color: var(--dark, #171d18) var(--light, #ffffff);
|
||||
}
|
||||
"#;
|
||||
#[derive(Copy, Clone, PartialEq, Default)]
|
||||
#[non_exhaustive]
|
||||
pub enum BadgeVariant {
|
||||
@@ -42,6 +78,8 @@ pub struct BadgeProps {
|
||||
#[component]
|
||||
pub fn Badge(props: BadgeProps) -> Element {
|
||||
rsx! {
|
||||
crate::OnceStyle { href: "dioxus-components/badge", css: STYLE }
|
||||
|
||||
BadgeElement {
|
||||
"padding": true,
|
||||
variant: props.variant,
|
||||
@@ -54,8 +92,10 @@ pub fn Badge(props: BadgeProps) -> Element {
|
||||
#[component]
|
||||
fn BadgeElement(props: BadgeProps) -> Element {
|
||||
rsx! {
|
||||
crate::OnceStyle { href: "dioxus-components/badge", css: STYLE }
|
||||
|
||||
span {
|
||||
class: Styles::dx_badge,
|
||||
class: "dx-badge",
|
||||
"data-style": props.variant.class(),
|
||||
..props.attributes,
|
||||
{props.children}
|
||||
@@ -66,6 +106,8 @@ fn BadgeElement(props: BadgeProps) -> Element {
|
||||
#[component]
|
||||
pub fn VerifiedIcon() -> Element {
|
||||
rsx! {
|
||||
crate::OnceStyle { href: "dioxus-components/badge", css: STYLE }
|
||||
|
||||
BadgeCheck {
|
||||
size: "12px",
|
||||
stroke: "var(--text)",
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
.dx-badge {
|
||||
display: inline-flex;
|
||||
min-width: 20px;
|
||||
height: 20px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 9999px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
gap: 4px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.dx-badge[padding="true"] {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.dx-badge[data-style="primary"] {
|
||||
background-color: var(--accent);
|
||||
color: var(--on-accent);
|
||||
}
|
||||
|
||||
.dx-badge[data-style="secondary"] {
|
||||
background-color: var(--surface-2);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dx-badge[data-style="outline"] {
|
||||
border: 1px solid var(--border);
|
||||
background-color: var(--surface);
|
||||
color: var(--text-2);
|
||||
}
|
||||
|
||||
.dx-badge[data-style="destructive"] {
|
||||
background-color: var(--danger);
|
||||
color: var(--dark, #171d18) var(--light, #ffffff);
|
||||
}
|
||||
@@ -2,9 +2,203 @@ use dioxus::prelude::*;
|
||||
use dioxus_primitives::dioxus_attributes::attributes;
|
||||
use dioxus_primitives::merge_attributes;
|
||||
|
||||
#[css_module("/src/components/button/style.css")]
|
||||
struct Styles;
|
||||
|
||||
const STYLE: &str = r#".dx-button {
|
||||
display: inline-flex;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
gap: 0.5rem;
|
||||
line-height: 1.25rem;
|
||||
outline: none;
|
||||
transition-duration: 150ms;
|
||||
transition-property: background-color, color, box-shadow, border-color, opacity;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dx-button svg {
|
||||
flex-shrink: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.dx-button svg:not([class*="size-"]) {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="xs"] {
|
||||
height: 1.5rem;
|
||||
padding: 0 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
gap: 0.25rem;
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="xs"]:has(> svg) {
|
||||
padding-inline: 0.375rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="xs"] svg:not([class*="size-"]) {
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="sm"] {
|
||||
height: 1.875rem;
|
||||
padding: 0 0.625rem;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="sm"]:has(> svg) {
|
||||
padding-inline: 0.5rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="default"] {
|
||||
height: 2rem;
|
||||
padding: 0.375rem 1rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="default"]:has(> svg) {
|
||||
padding-inline: 0.75rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="lg"] {
|
||||
height: 2.375rem;
|
||||
padding: 0 1.25rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="lg"]:has(> svg) {
|
||||
padding-inline: 1rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="icon"] {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="icon"] svg:not([class*="size-"]) {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="icon-xs"] {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="icon-xs"] svg:not([class*="size-"]) {
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="icon-sm"] {
|
||||
width: 1.875rem;
|
||||
height: 1.875rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="icon-sm"] svg:not([class*="size-"]) {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="icon-lg"] {
|
||||
width: 2.375rem;
|
||||
height: 2.375rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="icon-lg"] svg:not([class*="size-"]) {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.dx-button:disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.dx-button[data-style="primary"] {
|
||||
background-color: var(--accent);
|
||||
color: var(--on-accent);
|
||||
}
|
||||
|
||||
.dx-button[data-style="primary"]:not(:disabled):hover {
|
||||
background-color: color-mix(in oklab, var(--accent) 88%, var(--text));
|
||||
}
|
||||
|
||||
.dx-button[data-style="secondary"] {
|
||||
background-color: var(--surface-2);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dx-button[data-style="secondary"]:not(:disabled):hover {
|
||||
background-color: color-mix(in oklab, var(--surface-2) 85%, var(--text));
|
||||
}
|
||||
|
||||
.dx-button[data-style="ghost"] {
|
||||
background-color: transparent;
|
||||
color: var(--text-2);
|
||||
}
|
||||
|
||||
.dx-button[data-style="ghost"]:not(:disabled):hover {
|
||||
background-color: var(--surface-2);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dx-button[data-style="outline"] {
|
||||
border: 1px solid var(--border);
|
||||
background-color: var(--surface);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dx-button[data-style="outline"]:not(:disabled):hover {
|
||||
background-color: var(--surface-2);
|
||||
}
|
||||
|
||||
.dx-button[data-style="destructive"] {
|
||||
background-color: var(--danger);
|
||||
color: var(--dark, #171d18) var(--light, #ffffff);
|
||||
}
|
||||
|
||||
.dx-button[data-style="destructive"]:not(:disabled):hover {
|
||||
background-color: color-mix(in oklab, var(--danger) 88%, var(--text));
|
||||
}
|
||||
|
||||
.dx-button[data-style="destructive"]:focus-visible {
|
||||
box-shadow: 0 0 0 3px color-mix(in oklab, var(--danger) 40%, transparent);
|
||||
}
|
||||
|
||||
.dx-button[data-style="link"] {
|
||||
background-color: transparent;
|
||||
color: var(--accent-text);
|
||||
text-decoration: none;
|
||||
text-underline-offset: 0.25rem;
|
||||
}
|
||||
|
||||
.dx-button[data-style="link"]:not(:disabled):hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.dx-button:focus-visible {
|
||||
box-shadow: 0 0 0 3px color-mix(in oklab, var(--focus-ring) 45%, transparent);
|
||||
}
|
||||
|
||||
.dx-button[aria-invalid="true"] {
|
||||
border-color: var(--danger);
|
||||
}
|
||||
|
||||
.dx-button[aria-invalid="true"]:focus-visible {
|
||||
box-shadow: 0 0 0 3px color-mix(in oklab, var(--danger) 40%, transparent);
|
||||
}
|
||||
"#;
|
||||
#[derive(Copy, Clone, PartialEq, Default)]
|
||||
#[non_exhaustive]
|
||||
pub enum ButtonVariant {
|
||||
@@ -73,13 +267,15 @@ pub fn Button(
|
||||
children: Element,
|
||||
) -> Element {
|
||||
let base = attributes!(button {
|
||||
class: Styles::dx_button,
|
||||
class: "dx-button",
|
||||
"data-style": variant.class(),
|
||||
"data-size": size.class(),
|
||||
});
|
||||
let merged = merge_attributes(vec![base, attributes]);
|
||||
|
||||
rsx! {
|
||||
crate::OnceStyle { href: "dioxus-components/button", css: STYLE }
|
||||
|
||||
button {
|
||||
onclick: move |event| {
|
||||
if let Some(f) = &onclick {
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
.dx-button {
|
||||
display: inline-flex;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
gap: 0.5rem;
|
||||
line-height: 1.25rem;
|
||||
outline: none;
|
||||
transition-duration: 150ms;
|
||||
transition-property: background-color, color, box-shadow, border-color, opacity;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dx-button svg {
|
||||
flex-shrink: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.dx-button svg:not([class*="size-"]) {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="xs"] {
|
||||
height: 1.5rem;
|
||||
padding: 0 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
gap: 0.25rem;
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="xs"]:has(> svg) {
|
||||
padding-inline: 0.375rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="xs"] svg:not([class*="size-"]) {
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="sm"] {
|
||||
height: 1.875rem;
|
||||
padding: 0 0.625rem;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="sm"]:has(> svg) {
|
||||
padding-inline: 0.5rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="default"] {
|
||||
height: 2rem;
|
||||
padding: 0.375rem 1rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="default"]:has(> svg) {
|
||||
padding-inline: 0.75rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="lg"] {
|
||||
height: 2.375rem;
|
||||
padding: 0 1.25rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="lg"]:has(> svg) {
|
||||
padding-inline: 1rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="icon"] {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="icon"] svg:not([class*="size-"]) {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="icon-xs"] {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="icon-xs"] svg:not([class*="size-"]) {
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="icon-sm"] {
|
||||
width: 1.875rem;
|
||||
height: 1.875rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="icon-sm"] svg:not([class*="size-"]) {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="icon-lg"] {
|
||||
width: 2.375rem;
|
||||
height: 2.375rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="icon-lg"] svg:not([class*="size-"]) {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.dx-button:disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.dx-button[data-style="primary"] {
|
||||
background-color: var(--accent);
|
||||
color: var(--on-accent);
|
||||
}
|
||||
|
||||
.dx-button[data-style="primary"]:not(:disabled):hover {
|
||||
background-color: color-mix(in oklab, var(--accent) 88%, var(--text));
|
||||
}
|
||||
|
||||
.dx-button[data-style="secondary"] {
|
||||
background-color: var(--surface-2);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dx-button[data-style="secondary"]:not(:disabled):hover {
|
||||
background-color: color-mix(in oklab, var(--surface-2) 85%, var(--text));
|
||||
}
|
||||
|
||||
.dx-button[data-style="ghost"] {
|
||||
background-color: transparent;
|
||||
color: var(--text-2);
|
||||
}
|
||||
|
||||
.dx-button[data-style="ghost"]:not(:disabled):hover {
|
||||
background-color: var(--surface-2);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dx-button[data-style="outline"] {
|
||||
border: 1px solid var(--border);
|
||||
background-color: var(--surface);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dx-button[data-style="outline"]:not(:disabled):hover {
|
||||
background-color: var(--surface-2);
|
||||
}
|
||||
|
||||
.dx-button[data-style="destructive"] {
|
||||
background-color: var(--danger);
|
||||
color: var(--dark, #171d18) var(--light, #ffffff);
|
||||
}
|
||||
|
||||
.dx-button[data-style="destructive"]:not(:disabled):hover {
|
||||
background-color: color-mix(in oklab, var(--danger) 88%, var(--text));
|
||||
}
|
||||
|
||||
.dx-button[data-style="destructive"]:focus-visible {
|
||||
box-shadow: 0 0 0 3px color-mix(in oklab, var(--danger) 40%, transparent);
|
||||
}
|
||||
|
||||
.dx-button[data-style="link"] {
|
||||
background-color: transparent;
|
||||
color: var(--accent-text);
|
||||
text-decoration: none;
|
||||
text-underline-offset: 0.25rem;
|
||||
}
|
||||
|
||||
.dx-button[data-style="link"]:not(:disabled):hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.dx-button:focus-visible {
|
||||
box-shadow: 0 0 0 3px color-mix(in oklab, var(--focus-ring) 45%, transparent);
|
||||
}
|
||||
|
||||
.dx-button[aria-invalid="true"] {
|
||||
border-color: var(--danger);
|
||||
}
|
||||
|
||||
.dx-button[aria-invalid="true"]:focus-visible {
|
||||
box-shadow: 0 0 0 3px color-mix(in oklab, var(--danger) 40%, transparent);
|
||||
}
|
||||
@@ -1,16 +1,69 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[css_module("/src/components/card/style.css")]
|
||||
struct Styles;
|
||||
|
||||
const STYLE: &str = r#".dx-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem 0;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0.5rem;
|
||||
background-color: var(--surface);
|
||||
box-shadow: 0 1px 0 rgb(0 0 0 / 8%);
|
||||
color: var(--text);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.dx-card-header {
|
||||
display: grid;
|
||||
align-items: start;
|
||||
padding: 0 1rem;
|
||||
gap: 0.375rem;
|
||||
grid-auto-rows: min-content;
|
||||
grid-template-rows: auto auto;
|
||||
}
|
||||
|
||||
.dx-card-header:has([data-slot="card-action"]) {
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
|
||||
.dx-card-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.dx-card-description {
|
||||
color: var(--text-2);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
.dx-card-action {
|
||||
grid-column-start: 2;
|
||||
grid-row: 1 / span 2;
|
||||
place-self: start end;
|
||||
}
|
||||
|
||||
.dx-card-content {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.dx-card-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
"#;
|
||||
#[component]
|
||||
pub fn Card(
|
||||
#[props(extends=GlobalAttributes)] attributes: Vec<Attribute>,
|
||||
children: Element,
|
||||
) -> Element {
|
||||
rsx! {
|
||||
crate::OnceStyle { href: "dioxus-components/card", css: STYLE }
|
||||
|
||||
div {
|
||||
class: Styles::dx_card,
|
||||
class: "dx-card",
|
||||
"data-slot": "card",
|
||||
..attributes,
|
||||
{children}
|
||||
@@ -24,8 +77,10 @@ pub fn CardHeader(
|
||||
children: Element,
|
||||
) -> Element {
|
||||
rsx! {
|
||||
crate::OnceStyle { href: "dioxus-components/card", css: STYLE }
|
||||
|
||||
div {
|
||||
class: Styles::dx_card_header,
|
||||
class: "dx-card-header",
|
||||
"data-slot": "card-header",
|
||||
..attributes,
|
||||
{children}
|
||||
@@ -39,8 +94,10 @@ pub fn CardTitle(
|
||||
children: Element,
|
||||
) -> Element {
|
||||
rsx! {
|
||||
crate::OnceStyle { href: "dioxus-components/card", css: STYLE }
|
||||
|
||||
div {
|
||||
class: Styles::dx_card_title,
|
||||
class: "dx-card-title",
|
||||
"data-slot": "card-title",
|
||||
..attributes,
|
||||
{children}
|
||||
@@ -54,8 +111,10 @@ pub fn CardDescription(
|
||||
children: Element,
|
||||
) -> Element {
|
||||
rsx! {
|
||||
crate::OnceStyle { href: "dioxus-components/card", css: STYLE }
|
||||
|
||||
div {
|
||||
class: Styles::dx_card_description,
|
||||
class: "dx-card-description",
|
||||
"data-slot": "card-description",
|
||||
..attributes,
|
||||
{children}
|
||||
@@ -69,8 +128,10 @@ pub fn CardAction(
|
||||
children: Element,
|
||||
) -> Element {
|
||||
rsx! {
|
||||
crate::OnceStyle { href: "dioxus-components/card", css: STYLE }
|
||||
|
||||
div {
|
||||
class: Styles::dx_card_action,
|
||||
class: "dx-card-action",
|
||||
"data-slot": "card-action",
|
||||
..attributes,
|
||||
{children}
|
||||
@@ -84,8 +145,10 @@ pub fn CardContent(
|
||||
children: Element,
|
||||
) -> Element {
|
||||
rsx! {
|
||||
crate::OnceStyle { href: "dioxus-components/card", css: STYLE }
|
||||
|
||||
div {
|
||||
class: Styles::dx_card_content,
|
||||
class: "dx-card-content",
|
||||
"data-slot": "card-content",
|
||||
..attributes,
|
||||
{children}
|
||||
@@ -99,8 +162,10 @@ pub fn CardFooter(
|
||||
children: Element,
|
||||
) -> Element {
|
||||
rsx! {
|
||||
crate::OnceStyle { href: "dioxus-components/card", css: STYLE }
|
||||
|
||||
div {
|
||||
class: Styles::dx_card_footer,
|
||||
class: "dx-card-footer",
|
||||
"data-slot": "card-footer",
|
||||
..attributes,
|
||||
{children}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
.dx-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem 0;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0.5rem;
|
||||
background-color: var(--surface);
|
||||
box-shadow: 0 1px 0 rgb(0 0 0 / 8%);
|
||||
color: var(--text);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.dx-card-header {
|
||||
display: grid;
|
||||
align-items: start;
|
||||
padding: 0 1rem;
|
||||
gap: 0.375rem;
|
||||
grid-auto-rows: min-content;
|
||||
grid-template-rows: auto auto;
|
||||
}
|
||||
|
||||
.dx-card-header:has([data-slot="card-action"]) {
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
|
||||
.dx-card-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.dx-card-description {
|
||||
color: var(--text-2);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
.dx-card-action {
|
||||
grid-column-start: 2;
|
||||
grid-row: 1 / span 2;
|
||||
place-self: start end;
|
||||
}
|
||||
|
||||
.dx-card-content {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.dx-card-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
@@ -2,14 +2,49 @@ use dioxus::prelude::*;
|
||||
use dioxus_icons::lucide::Check;
|
||||
use dioxus_primitives::checkbox::{self, CheckboxProps};
|
||||
|
||||
#[css_module("/src/components/checkbox/style.css")]
|
||||
struct Styles;
|
||||
|
||||
const STYLE: &str = r#".dx-checkbox {
|
||||
width: 1.125rem;
|
||||
height: 1.125rem;
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 0.25rem;
|
||||
margin: 0;
|
||||
background-color: var(--surface-2);
|
||||
box-shadow: inset 0 0 0 1px var(--border);
|
||||
color: var(--on-accent);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dx-checkbox-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.dx-checkbox[data-state="checked"] {
|
||||
background-color: var(--accent);
|
||||
box-shadow: none;
|
||||
color: var(--on-accent);
|
||||
}
|
||||
|
||||
.dx-checkbox:focus-visible {
|
||||
box-shadow: 0 0 0 2px color-mix(in oklab, var(--accent) 45%, transparent);
|
||||
}
|
||||
|
||||
.dx-checkbox[data-disabled="true"] {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
"#;
|
||||
#[component]
|
||||
pub fn Checkbox(props: CheckboxProps) -> Element {
|
||||
rsx! {
|
||||
crate::OnceStyle { href: "dioxus-components/checkbox", css: STYLE }
|
||||
|
||||
checkbox::Checkbox {
|
||||
class: Styles::dx_checkbox,
|
||||
class: "dx-checkbox",
|
||||
checked: props.checked,
|
||||
default_checked: props.default_checked,
|
||||
required: props.required,
|
||||
@@ -18,7 +53,7 @@ pub fn Checkbox(props: CheckboxProps) -> Element {
|
||||
value: props.value,
|
||||
on_checked_change: props.on_checked_change,
|
||||
attributes: props.attributes,
|
||||
checkbox::CheckboxIndicator { class: Styles::dx_checkbox_indicator,
|
||||
checkbox::CheckboxIndicator { class: "dx-checkbox-indicator",
|
||||
Check { size: "1rem" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
.dx-checkbox {
|
||||
width: 1.125rem;
|
||||
height: 1.125rem;
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 0.25rem;
|
||||
margin: 0;
|
||||
background-color: var(--surface-2);
|
||||
box-shadow: inset 0 0 0 1px var(--border);
|
||||
color: var(--on-accent);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dx-checkbox-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.dx-checkbox[data-state="checked"] {
|
||||
background-color: var(--accent);
|
||||
box-shadow: none;
|
||||
color: var(--on-accent);
|
||||
}
|
||||
|
||||
.dx-checkbox:focus-visible {
|
||||
box-shadow: 0 0 0 2px color-mix(in oklab, var(--accent) 45%, transparent);
|
||||
}
|
||||
|
||||
.dx-checkbox[data-disabled="true"] {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::{Button, ButtonVariant, Dialog, DialogDescription, DialogTitle};
|
||||
|
||||
|
||||
const STYLE: &str = r#".dx-control {
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 5.5rem;
|
||||
min-height: 5.5rem;
|
||||
box-sizing: border-box;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.85rem 0.65rem 0.7rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0.375rem;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 4px 16px rgb(0 0 0 / 16%);
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
gap: 0.45rem;
|
||||
outline: none;
|
||||
transition: background-color 120ms ease, border-color 120ms ease, box-shadow 120ms ease,
|
||||
transform 120ms ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.dx-control:hover:not(:disabled):not([data-kind="display"]) {
|
||||
background: var(--surface-2);
|
||||
border-color: color-mix(in oklab, var(--accent) 45%, var(--border));
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.dx-control:focus-visible {
|
||||
box-shadow: 0 0 0 2px color-mix(in oklab, var(--accent) 40%, transparent),
|
||||
0 4px 16px rgb(0 0 0 / 16%);
|
||||
}
|
||||
|
||||
.dx-control[data-enabled="true"] {
|
||||
border-color: color-mix(in oklab, var(--accent) 70%, var(--border));
|
||||
background: color-mix(in oklab, var(--accent) 16%, var(--surface));
|
||||
}
|
||||
|
||||
.dx-control[data-kind="display"] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.dx-control:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.dx-control-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
border-radius: 0.375rem;
|
||||
background: var(--surface-2);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dx-control[data-enabled="true"] .dx-control-icon {
|
||||
background: color-mix(in oklab, var(--accent) 28%, var(--surface-2));
|
||||
color: var(--accent-text);
|
||||
}
|
||||
|
||||
.dx-control-icon svg {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
}
|
||||
|
||||
.dx-control-name {
|
||||
color: var(--text);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dx-control-value {
|
||||
color: var(--text-2);
|
||||
font-size: 0.7rem;
|
||||
line-height: 1.2;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dx-control-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 0.75rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
"#;
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Default)]
|
||||
#[non_exhaustive]
|
||||
pub enum ControlKind {
|
||||
/// Click toggles an on/off state (optional confirm).
|
||||
#[default]
|
||||
Toggle,
|
||||
/// Click triggers an action (optional confirm).
|
||||
Action,
|
||||
/// Read-only display of a setting/value.
|
||||
Display,
|
||||
}
|
||||
|
||||
impl ControlKind {
|
||||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
ControlKind::Toggle => "toggle",
|
||||
ControlKind::Action => "action",
|
||||
ControlKind::Display => "display",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Control(
|
||||
/// Visible label under the icon.
|
||||
name: String,
|
||||
#[props(default)]
|
||||
kind: ControlKind,
|
||||
/// Toggle enabled state (Toggle kind).
|
||||
#[props(default)]
|
||||
enabled: bool,
|
||||
/// Optional value line (useful for Display, also ok for others).
|
||||
value: Option<String>,
|
||||
/// If set, show a confirmation dialog before toggling/acting.
|
||||
confirm: Option<String>,
|
||||
/// Fired for Action after confirm (or immediately if no confirm).
|
||||
onclick: Option<EventHandler<()>>,
|
||||
/// Fired for Toggle with the new enabled value after confirm (or immediately).
|
||||
on_enabled_change: Option<EventHandler<bool>>,
|
||||
/// Icon element (e.g. a dioxus-icons lucide component).
|
||||
children: Element,
|
||||
) -> Element {
|
||||
let mut confirm_open = use_signal(|| false);
|
||||
let needs_confirm = confirm.is_some();
|
||||
|
||||
let do_commit = move || {
|
||||
match kind {
|
||||
ControlKind::Toggle => {
|
||||
if let Some(cb) = on_enabled_change {
|
||||
cb.call(!enabled);
|
||||
}
|
||||
}
|
||||
ControlKind::Action => {
|
||||
if let Some(cb) = onclick {
|
||||
cb.call(());
|
||||
}
|
||||
}
|
||||
ControlKind::Display => {}
|
||||
}
|
||||
};
|
||||
|
||||
rsx! {
|
||||
crate::OnceStyle { href: "dioxus-components/control", css: STYLE }
|
||||
|
||||
button {
|
||||
class: "dx-control",
|
||||
"data-slot": "control",
|
||||
"data-kind": kind.as_str(),
|
||||
"data-enabled": enabled.to_string(),
|
||||
r#type: "button",
|
||||
disabled: kind == ControlKind::Display,
|
||||
onclick: move |_| {
|
||||
if kind == ControlKind::Display {
|
||||
return;
|
||||
}
|
||||
if needs_confirm {
|
||||
confirm_open.set(true);
|
||||
} else {
|
||||
do_commit();
|
||||
}
|
||||
},
|
||||
span { class: "dx-control-icon", {children} }
|
||||
span { class: "dx-control-name", "{name}" }
|
||||
if let Some(val) = &value {
|
||||
span { class: "dx-control-value", "{val}" }
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(msg) = confirm {
|
||||
Dialog {
|
||||
open: Some(confirm_open()),
|
||||
on_open_change: move |open| confirm_open.set(open),
|
||||
DialogTitle { "Confirm" }
|
||||
DialogDescription { "{msg}" }
|
||||
div { class: "dx-control-actions",
|
||||
Button {
|
||||
variant: ButtonVariant::Ghost,
|
||||
onclick: move |_| confirm_open.set(false),
|
||||
"Cancel"
|
||||
}
|
||||
Button {
|
||||
variant: ButtonVariant::Primary,
|
||||
onclick: move |_| {
|
||||
confirm_open.set(false);
|
||||
do_commit();
|
||||
},
|
||||
"Confirm"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
mod component;
|
||||
pub use component::*;
|
||||
@@ -2,19 +2,120 @@ use dioxus::prelude::*;
|
||||
use dioxus_primitives::dialog::{self, DialogDescriptionProps, DialogRootProps, DialogTitleProps};
|
||||
use dioxus_primitives::{dioxus_attributes::attributes, merge_attributes};
|
||||
|
||||
#[css_module("/src/components/dialog/style.css")]
|
||||
struct Styles;
|
||||
|
||||
const STYLE: &str = r#".dx-dialog-backdrop {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
background: rgb(23 29 24 / 55%);
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
will-change: opacity;
|
||||
}
|
||||
|
||||
.dx-dialog-backdrop[data-state="closed"] {
|
||||
animation: dx-dialog-backdrop-animate-out 150ms ease-in forwards;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes dx-dialog-backdrop-animate-out {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.dx-dialog-backdrop[data-state="open"] {
|
||||
animation: dx-dialog-backdrop-animate-in 150ms ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes dx-dialog-backdrop-animate-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dx-dialog {
|
||||
position: fixed;
|
||||
z-index: 1001;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
max-width: calc(100% - 2rem);
|
||||
box-sizing: border-box;
|
||||
flex-direction: column;
|
||||
padding: 1.5rem 1.25rem 1.25rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0.5rem;
|
||||
margin: 0;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 8px 24px rgb(0 0 0 / 35%);
|
||||
color: var(--text);
|
||||
font-family: inherit;
|
||||
gap: 0.75rem;
|
||||
text-align: center;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.dx-dialog-title {
|
||||
margin: 0;
|
||||
color: var(--text);
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.dx-dialog-description {
|
||||
margin: 0;
|
||||
color: var(--text-2);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
@media (width >= 40rem) {
|
||||
.dx-dialog {
|
||||
max-width: 28rem;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.dx-dialog-close {
|
||||
position: absolute;
|
||||
top: 0.75rem;
|
||||
right: 0.75rem;
|
||||
align-self: flex-start;
|
||||
padding: 0;
|
||||
border: none;
|
||||
margin: 0;
|
||||
background: none;
|
||||
color: var(--text-2);
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.dx-dialog-close:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
"#;
|
||||
#[component]
|
||||
pub fn Dialog(props: DialogRootProps) -> Element {
|
||||
let base = attributes!(div {
|
||||
class: Styles::dx_dialog,
|
||||
class: "dx-dialog",
|
||||
});
|
||||
let merged = merge_attributes(vec![base, props.attributes]);
|
||||
|
||||
rsx! {
|
||||
crate::OnceStyle { href: "dioxus-components/dialog", css: STYLE }
|
||||
|
||||
dialog::DialogRoot {
|
||||
class: Styles::dx_dialog_backdrop,
|
||||
class: "dx-dialog-backdrop",
|
||||
id: props.id,
|
||||
is_modal: props.is_modal,
|
||||
open: props.open,
|
||||
@@ -32,11 +133,13 @@ pub fn Dialog(props: DialogRootProps) -> Element {
|
||||
#[component]
|
||||
pub fn DialogTitle(props: DialogTitleProps) -> Element {
|
||||
let base = attributes!(h2 {
|
||||
class: Styles::dx_dialog_title,
|
||||
class: "dx-dialog-title",
|
||||
});
|
||||
let merged = merge_attributes(vec![base, props.attributes]);
|
||||
|
||||
rsx! {
|
||||
crate::OnceStyle { href: "dioxus-components/dialog", css: STYLE }
|
||||
|
||||
dialog::DialogTitle {
|
||||
id: props.id,
|
||||
attributes: merged,
|
||||
@@ -48,11 +151,13 @@ pub fn DialogTitle(props: DialogTitleProps) -> Element {
|
||||
#[component]
|
||||
pub fn DialogDescription(props: DialogDescriptionProps) -> Element {
|
||||
let base = attributes!(p {
|
||||
class: Styles::dx_dialog_description,
|
||||
class: "dx-dialog-description",
|
||||
});
|
||||
let merged = merge_attributes(vec![base, props.attributes]);
|
||||
|
||||
rsx! {
|
||||
crate::OnceStyle { href: "dioxus-components/dialog", css: STYLE }
|
||||
|
||||
dialog::DialogDescription {
|
||||
id: props.id,
|
||||
attributes: merged,
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
.dx-dialog-backdrop {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
background: rgb(23 29 24 / 55%);
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
will-change: opacity;
|
||||
}
|
||||
|
||||
.dx-dialog-backdrop[data-state="closed"] {
|
||||
animation: dx-dialog-backdrop-animate-out 150ms ease-in forwards;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes dx-dialog-backdrop-animate-out {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.dx-dialog-backdrop[data-state="open"] {
|
||||
animation: dx-dialog-backdrop-animate-in 150ms ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes dx-dialog-backdrop-animate-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dx-dialog {
|
||||
position: fixed;
|
||||
z-index: 1001;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
max-width: calc(100% - 2rem);
|
||||
box-sizing: border-box;
|
||||
flex-direction: column;
|
||||
padding: 1.5rem 1.25rem 1.25rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0.5rem;
|
||||
margin: 0;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 8px 24px rgb(0 0 0 / 35%);
|
||||
color: var(--text);
|
||||
font-family: inherit;
|
||||
gap: 0.75rem;
|
||||
text-align: center;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.dx-dialog-title {
|
||||
margin: 0;
|
||||
color: var(--text);
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.dx-dialog-description {
|
||||
margin: 0;
|
||||
color: var(--text-2);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
@media (width >= 40rem) {
|
||||
.dx-dialog {
|
||||
max-width: 28rem;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.dx-dialog-close {
|
||||
position: absolute;
|
||||
top: 0.75rem;
|
||||
right: 0.75rem;
|
||||
align-self: flex-start;
|
||||
padding: 0;
|
||||
border: none;
|
||||
margin: 0;
|
||||
background: none;
|
||||
color: var(--text-2);
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.dx-dialog-close:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
@@ -1,7 +1,46 @@
|
||||
use dioxus::prelude::*;
|
||||
#[css_module("/src/components/input/style.css")]
|
||||
struct Styles;
|
||||
|
||||
const STYLE: &str = r#".dx-input {
|
||||
position: relative;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
background-color: var(--surface-2);
|
||||
box-shadow: inset 0 0 0 1px var(--border);
|
||||
color: var(--text);
|
||||
font-family: inherit;
|
||||
font-size: 0.875rem;
|
||||
gap: 0.25rem;
|
||||
outline: none;
|
||||
transition: background-color 100ms ease-out, box-shadow 100ms ease-out;
|
||||
}
|
||||
|
||||
.dx-input::placeholder {
|
||||
color: var(--text-2);
|
||||
}
|
||||
|
||||
.dx-input:disabled {
|
||||
color: var(--text-2);
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.dx-input:hover:not(:disabled) {
|
||||
background-color: color-mix(in oklab, var(--surface-2) 90%, var(--text));
|
||||
}
|
||||
|
||||
.dx-input:focus-visible {
|
||||
background-color: var(--surface);
|
||||
box-shadow: inset 0 0 0 1px var(--accent), 0 0 0 2px color-mix(in oklab, var(--accent) 35%, transparent);
|
||||
color: var(--text);
|
||||
outline: none;
|
||||
}
|
||||
"#;
|
||||
#[component]
|
||||
pub fn Input(
|
||||
oninput: Option<EventHandler<FormEvent>>,
|
||||
@@ -29,8 +68,10 @@ pub fn Input(
|
||||
children: Element,
|
||||
) -> Element {
|
||||
rsx! {
|
||||
crate::OnceStyle { href: "dioxus-components/input", css: STYLE }
|
||||
|
||||
input {
|
||||
class: Styles::dx_input,
|
||||
class: "dx-input",
|
||||
oninput: move |e| _ = oninput.map(|callback| callback(e)),
|
||||
onchange: move |e| _ = onchange.map(|callback| callback(e)),
|
||||
oninvalid: move |e| _ = oninvalid.map(|callback| callback(e)),
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
.dx-input {
|
||||
position: relative;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
background-color: var(--surface-2);
|
||||
box-shadow: inset 0 0 0 1px var(--border);
|
||||
color: var(--text);
|
||||
font-family: inherit;
|
||||
font-size: 0.875rem;
|
||||
gap: 0.25rem;
|
||||
outline: none;
|
||||
transition: background-color 100ms ease-out, box-shadow 100ms ease-out;
|
||||
}
|
||||
|
||||
.dx-input::placeholder {
|
||||
color: var(--text-2);
|
||||
}
|
||||
|
||||
.dx-input:disabled {
|
||||
color: var(--text-2);
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.dx-input:hover:not(:disabled) {
|
||||
background-color: color-mix(in oklab, var(--surface-2) 90%, var(--text));
|
||||
}
|
||||
|
||||
.dx-input:focus-visible {
|
||||
background-color: var(--surface);
|
||||
box-shadow: inset 0 0 0 1px var(--accent), 0 0 0 2px color-mix(in oklab, var(--accent) 35%, transparent);
|
||||
color: var(--text);
|
||||
outline: none;
|
||||
}
|
||||
@@ -1,13 +1,23 @@
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_primitives::label::{self, LabelProps};
|
||||
#[css_module("/src/components/label/style.css")]
|
||||
struct Styles;
|
||||
|
||||
const STYLE: &str = r#".dx-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--text-2);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.01em;
|
||||
line-height: 1.2;
|
||||
}
|
||||
"#;
|
||||
#[component]
|
||||
pub fn Label(props: LabelProps) -> Element {
|
||||
rsx! {
|
||||
crate::OnceStyle { href: "dioxus-components/label", css: STYLE }
|
||||
|
||||
label::Label {
|
||||
class: Styles::dx_label,
|
||||
class: "dx-label",
|
||||
html_for: props.html_for,
|
||||
attributes: props.attributes,
|
||||
{props.children}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
.dx-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--text-2);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.01em;
|
||||
line-height: 1.2;
|
||||
}
|
||||
@@ -3,9 +3,11 @@ pub mod badge;
|
||||
pub mod button;
|
||||
pub mod card;
|
||||
pub mod checkbox;
|
||||
pub mod control;
|
||||
pub mod dialog;
|
||||
pub mod input;
|
||||
pub mod label;
|
||||
pub mod prompt;
|
||||
pub mod scroll_area;
|
||||
pub mod separator;
|
||||
pub mod switch;
|
||||
@@ -16,9 +18,11 @@ pub use badge::*;
|
||||
pub use button::*;
|
||||
pub use card::*;
|
||||
pub use checkbox::*;
|
||||
pub use control::*;
|
||||
pub use dialog::*;
|
||||
pub use input::*;
|
||||
pub use label::*;
|
||||
pub use prompt::*;
|
||||
pub use scroll_area::*;
|
||||
pub use separator::*;
|
||||
pub use switch::*;
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
const STYLE: &str = r#".dx-prompt {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0.4rem 0.75rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0.375rem;
|
||||
background: var(--surface);
|
||||
box-shadow: none;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.dx-prompt-input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-height: 1.5rem;
|
||||
max-height: 1.5rem;
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
border: none;
|
||||
margin: 0;
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
font-family: inherit;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5rem;
|
||||
outline: none;
|
||||
overflow: hidden;
|
||||
resize: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dx-prompt-input::placeholder {
|
||||
color: var(--text-2);
|
||||
}
|
||||
"#;
|
||||
|
||||
#[component]
|
||||
pub fn Prompt(
|
||||
/// Controlled value of the prompt text.
|
||||
#[props(default)]
|
||||
value: String,
|
||||
/// Placeholder shown when empty.
|
||||
#[props(default = "Ask anything…".to_string())]
|
||||
placeholder: String,
|
||||
/// Autofocus the textarea when mounted (default true).
|
||||
#[props(default = true)]
|
||||
autofocus: bool,
|
||||
oninput: Option<EventHandler<FormEvent>>,
|
||||
onkeydown: Option<EventHandler<KeyboardEvent>>,
|
||||
#[props(extends = GlobalAttributes)]
|
||||
attributes: Vec<Attribute>,
|
||||
) -> Element {
|
||||
let mut textarea_el = use_signal(|| None::<std::rc::Rc<MountedData>>);
|
||||
|
||||
use_effect(move || {
|
||||
if !autofocus {
|
||||
return;
|
||||
}
|
||||
if let Some(el) = textarea_el() {
|
||||
spawn(async move {
|
||||
let _ = el.set_focus(true).await;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
rsx! {
|
||||
crate::OnceStyle { href: "dioxus-components/prompt", css: STYLE }
|
||||
|
||||
div {
|
||||
class: "dx-prompt",
|
||||
"data-slot": "prompt",
|
||||
..attributes,
|
||||
textarea {
|
||||
class: "dx-prompt-input",
|
||||
"data-slot": "prompt-input",
|
||||
value,
|
||||
placeholder,
|
||||
rows: 1,
|
||||
onmounted: move |e| textarea_el.set(Some(e.data())),
|
||||
oninput: move |e| {
|
||||
if let Some(cb) = &oninput {
|
||||
cb.call(e);
|
||||
}
|
||||
},
|
||||
onkeydown: move |e| {
|
||||
// Enter does nothing for now (no submit).
|
||||
if e.key() == Key::Enter {
|
||||
e.prevent_default();
|
||||
}
|
||||
if let Some(cb) = &onkeydown {
|
||||
cb.call(e);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
mod component;
|
||||
pub use component::*;
|
||||
@@ -1 +0,0 @@
|
||||
/* Scroll area uses unstyled dioxus-primitives; no extra styles for MVP. */
|
||||
@@ -2,17 +2,31 @@ use dioxus::prelude::*;
|
||||
use dioxus_primitives::separator::{self, SeparatorProps};
|
||||
use dioxus_primitives::{dioxus_attributes::attributes, merge_attributes};
|
||||
|
||||
#[css_module("/src/components/separator/style.css")]
|
||||
struct Styles;
|
||||
|
||||
const STYLE: &str = r#".dx-separator {
|
||||
background-color: var(--border);
|
||||
}
|
||||
|
||||
.dx-separator[data-orientation="horizontal"] {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.dx-separator[data-orientation="vertical"] {
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
}
|
||||
"#;
|
||||
#[component]
|
||||
pub fn Separator(props: SeparatorProps) -> Element {
|
||||
let base = attributes!(div {
|
||||
class: Styles::dx_separator,
|
||||
class: "dx-separator",
|
||||
});
|
||||
let merged = merge_attributes(vec![base, props.attributes]);
|
||||
|
||||
rsx! {
|
||||
crate::OnceStyle { href: "dioxus-components/separator", css: STYLE }
|
||||
|
||||
separator::Separator {
|
||||
horizontal: props.horizontal,
|
||||
decorative: props.decorative,
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
.dx-separator {
|
||||
background-color: var(--border);
|
||||
}
|
||||
|
||||
.dx-separator[data-orientation="horizontal"] {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.dx-separator[data-orientation="vertical"] {
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -1,14 +1,56 @@
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_primitives::switch::{self, SwitchProps};
|
||||
|
||||
#[css_module("/src/components/switch/style.css")]
|
||||
struct Styles;
|
||||
|
||||
const STYLE: &str = r#".dx-switch {
|
||||
all: unset;
|
||||
position: relative;
|
||||
width: 2rem;
|
||||
height: 1.15rem;
|
||||
border-radius: 9999px;
|
||||
background-color: var(--surface-2);
|
||||
box-shadow: inset 0 0 0 1px var(--border);
|
||||
cursor: pointer;
|
||||
transition: background-color 150ms, box-shadow 150ms;
|
||||
}
|
||||
|
||||
.dx-switch[data-state="checked"] {
|
||||
background-color: var(--accent);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.dx-switch:focus-visible {
|
||||
box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 45%, transparent);
|
||||
}
|
||||
|
||||
.dx-switch-thumb {
|
||||
display: block;
|
||||
width: calc(1.15rem - 2px);
|
||||
height: calc(1.15rem - 2px);
|
||||
border-radius: 9999px;
|
||||
background-color: var(--text);
|
||||
transform: translateX(1px);
|
||||
transition: transform 150ms, background-color 150ms;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.dx-switch[data-state="checked"] .dx-switch-thumb {
|
||||
background-color: var(--on-accent);
|
||||
transform: translateX(calc(2rem - 1px - (1.15rem - 2px)));
|
||||
}
|
||||
|
||||
.dx-switch[data-disabled="true"] {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
"#;
|
||||
#[component]
|
||||
pub fn Switch(props: SwitchProps) -> Element {
|
||||
rsx! {
|
||||
crate::OnceStyle { href: "dioxus-components/switch", css: STYLE }
|
||||
|
||||
switch::Switch {
|
||||
class: Styles::dx_switch,
|
||||
class: "dx-switch",
|
||||
checked: props.checked,
|
||||
default_checked: props.default_checked,
|
||||
disabled: props.disabled,
|
||||
@@ -17,7 +59,7 @@ pub fn Switch(props: SwitchProps) -> Element {
|
||||
value: props.value,
|
||||
on_checked_change: props.on_checked_change,
|
||||
attributes: props.attributes,
|
||||
switch::SwitchThumb { class: Styles::dx_switch_thumb }
|
||||
switch::SwitchThumb { class: "dx-switch-thumb" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
.dx-switch {
|
||||
all: unset;
|
||||
position: relative;
|
||||
width: 2rem;
|
||||
height: 1.15rem;
|
||||
border-radius: 9999px;
|
||||
background-color: var(--surface-2);
|
||||
box-shadow: inset 0 0 0 1px var(--border);
|
||||
cursor: pointer;
|
||||
transition: background-color 150ms, box-shadow 150ms;
|
||||
}
|
||||
|
||||
.dx-switch[data-state="checked"] {
|
||||
background-color: var(--accent);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.dx-switch:focus-visible {
|
||||
box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 45%, transparent);
|
||||
}
|
||||
|
||||
.dx-switch-thumb {
|
||||
display: block;
|
||||
width: calc(1.15rem - 2px);
|
||||
height: calc(1.15rem - 2px);
|
||||
border-radius: 9999px;
|
||||
background-color: var(--text);
|
||||
transform: translateX(1px);
|
||||
transition: transform 150ms, background-color 150ms;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.dx-switch[data-state="checked"] .dx-switch-thumb {
|
||||
background-color: var(--on-accent);
|
||||
transform: translateX(calc(2rem - 1px - (1.15rem - 2px)));
|
||||
}
|
||||
|
||||
.dx-switch[data-disabled="true"] {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
@@ -1,8 +1,91 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[css_module("/src/components/textarea/style.css")]
|
||||
struct Styles;
|
||||
|
||||
const STYLE: &str = r#".dx-textarea {
|
||||
width: 100%;
|
||||
min-height: 4rem;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
margin: 0;
|
||||
appearance: none;
|
||||
background: none;
|
||||
color: var(--text);
|
||||
font-family: inherit;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
outline: none;
|
||||
resize: vertical;
|
||||
transition: background-color 100ms ease-out, border-color 100ms ease-out, box-shadow 100ms ease-out;
|
||||
}
|
||||
|
||||
.dx-textarea:disabled {
|
||||
color: var(--text-2);
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.dx-textarea::placeholder {
|
||||
color: var(--text-2);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="default"] {
|
||||
background: var(--surface-2);
|
||||
box-shadow: inset 0 0 0 1px var(--border);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="default"]:hover:not(:disabled),
|
||||
.dx-textarea[data-style="default"]:focus {
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="default"]:focus {
|
||||
box-shadow: inset 0 0 0 1px var(--accent), 0 0 0 2px color-mix(in oklab, var(--accent) 35%, transparent);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="fade"] {
|
||||
background: var(--surface-2);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="fade"]:hover:not(:disabled),
|
||||
.dx-textarea[data-style="fade"]:focus {
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="outline"] {
|
||||
border: 1px solid var(--border);
|
||||
background-color: var(--surface);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="outline"]:hover:not(:disabled, :focus) {
|
||||
border-color: color-mix(in oklab, var(--border) 70%, var(--text));
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="outline"]:focus {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="outline"]:invalid,
|
||||
.dx-textarea[data-style="outline"][aria-invalid="true"] {
|
||||
border-color: var(--danger);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="ghost"] {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="ghost"]:hover:not(:disabled) {
|
||||
background-color: var(--surface-2);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="ghost"]:focus {
|
||||
box-shadow: inset 0 0 0 1px var(--accent);
|
||||
}
|
||||
"#;
|
||||
#[derive(Copy, Clone, PartialEq, Default)]
|
||||
#[non_exhaustive]
|
||||
pub enum TextareaVariant {
|
||||
@@ -52,8 +135,10 @@ pub fn Textarea(
|
||||
children: Element,
|
||||
) -> Element {
|
||||
rsx! {
|
||||
crate::OnceStyle { href: "dioxus-components/textarea", css: STYLE }
|
||||
|
||||
textarea {
|
||||
class: Styles::dx_textarea,
|
||||
class: "dx-textarea",
|
||||
"data-slot": "textarea",
|
||||
"data-style": variant.class(),
|
||||
oninput: move |e| _ = oninput.map(|callback| callback(e)),
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
.dx-textarea {
|
||||
width: 100%;
|
||||
min-height: 4rem;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
margin: 0;
|
||||
appearance: none;
|
||||
background: none;
|
||||
color: var(--text);
|
||||
font-family: inherit;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
outline: none;
|
||||
resize: vertical;
|
||||
transition: background-color 100ms ease-out, border-color 100ms ease-out, box-shadow 100ms ease-out;
|
||||
}
|
||||
|
||||
.dx-textarea:disabled {
|
||||
color: var(--text-2);
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.dx-textarea::placeholder {
|
||||
color: var(--text-2);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="default"] {
|
||||
background: var(--surface-2);
|
||||
box-shadow: inset 0 0 0 1px var(--border);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="default"]:hover:not(:disabled),
|
||||
.dx-textarea[data-style="default"]:focus {
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="default"]:focus {
|
||||
box-shadow: inset 0 0 0 1px var(--accent), 0 0 0 2px color-mix(in oklab, var(--accent) 35%, transparent);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="fade"] {
|
||||
background: var(--surface-2);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="fade"]:hover:not(:disabled),
|
||||
.dx-textarea[data-style="fade"]:focus {
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="outline"] {
|
||||
border: 1px solid var(--border);
|
||||
background-color: var(--surface);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="outline"]:hover:not(:disabled, :focus) {
|
||||
border-color: color-mix(in oklab, var(--border) 70%, var(--text));
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="outline"]:focus {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="outline"]:invalid,
|
||||
.dx-textarea[data-style="outline"][aria-invalid="true"] {
|
||||
border-color: var(--danger);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="ghost"] {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="ghost"]:hover:not(:disabled) {
|
||||
background-color: var(--surface-2);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="ghost"]:focus {
|
||||
box-shadow: inset 0 0 0 1px var(--accent);
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
//! Styled Dioxus UI components using the mize color palette.
|
||||
//!
|
||||
//! Include [`THEME`] once at the app root (e.g. a stylesheet link), and set
|
||||
//! `html[data-theme="light"]` or `html[data-theme="dark"]` to switch themes.
|
||||
//! Include [`THEME_CSS`] once at the app root via `document::Style`, and set
|
||||
//! `html[data-theme="light"]` or `html[data-theme="dark"]` (or a scoped
|
||||
//! `data-theme` ancestor) to switch themes.
|
||||
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
@@ -10,5 +11,75 @@ use dioxus::prelude::*;
|
||||
pub mod components;
|
||||
pub use components::*;
|
||||
|
||||
/// Global theme stylesheet (`assets/theme.css`). Link this once in your app.
|
||||
pub const THEME: Asset = asset!("/assets/theme.css");
|
||||
/// Global theme CSS variables. Inject with `document::Style { {THEME_CSS} }`.
|
||||
pub const THEME_CSS: &str = r#"/* mize UI theme — include once; toggle via html[data-theme="light"|"dark"] */
|
||||
|
||||
html[data-theme="dark"] {
|
||||
--dark: initial;
|
||||
--light: ;
|
||||
}
|
||||
|
||||
html[data-theme="light"] {
|
||||
--dark: ;
|
||||
--light: initial;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme]) {
|
||||
--dark: initial;
|
||||
--light: ;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root:not([data-theme]) {
|
||||
--dark: ;
|
||||
--light: initial;
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg: var(--dark, #171D18) var(--light, #F7F8F4);
|
||||
--surface: var(--dark, #242D25) var(--light, #FFFFFF);
|
||||
--surface-2: var(--dark, #2C362D) var(--light, #EFF2EA);
|
||||
--text: var(--dark, #F2F4EA) var(--light, #20251C);
|
||||
--text-2: var(--dark, #AAB5A4) var(--light, #66705E);
|
||||
--border: var(--dark, #3A463A) var(--light, #DFE4D9);
|
||||
--accent: var(--dark, #D7E47D) var(--light, #FEC251);
|
||||
--accent-text: var(--dark, #D7E47D) var(--light, #79500F);
|
||||
--success: var(--dark, #B9D987) var(--light, #526F32);
|
||||
--info: var(--dark, #B9E4EB) var(--light, #376777);
|
||||
--warning: var(--dark, #FFD77F) var(--light, #A66B13);
|
||||
--danger: var(--dark, #FF9B8E) var(--light, #B94D42);
|
||||
|
||||
/* On-accent fill text: dark ink on light accent, dark surface on dark accent */
|
||||
--on-accent: var(--dark, #171D18) var(--light, #79500F);
|
||||
--focus-ring: var(--accent);
|
||||
}
|
||||
"#;
|
||||
|
||||
/// Injects a `<style>` into the document head exactly once.
|
||||
///
|
||||
/// Wrap `document::Style` in a component whose props are stable (`&'static str`)
|
||||
/// so parent re-renders do not diff Style props (which dioxus does not support
|
||||
/// and warns about).
|
||||
#[component]
|
||||
pub fn OnceStyle(href: &'static str, css: &'static str) -> Element {
|
||||
rsx! {
|
||||
document::Style {
|
||||
href: "{href}",
|
||||
{css}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience component that injects [`THEME_CSS`] into the document head.
|
||||
#[component]
|
||||
pub fn ThemeStyle() -> Element {
|
||||
rsx! {
|
||||
OnceStyle {
|
||||
href: "dioxus-components/theme",
|
||||
css: THEME_CSS,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user