Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/eight-kings-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@devup-ui/wasm": patch
---

Fix desctructing issue, Support for number in typo, Implement theme selector
5 changes: 5 additions & 0 deletions .changeset/quiet-rabbits-attend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@devup-ui/react": patch
---

Implement ThemeScript, useTheme, getTheme, setTheme
6 changes: 6 additions & 0 deletions .changeset/quiet-waves-confess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@devup-ui/webpack-plugin": patch
"@devup-ui/vite-plugin": patch
---

Update
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion apps/landing/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'sanitize.css'

import { ThemeHead } from '@devup-ui/react'
import type { Metadata } from 'next'

import { Footer } from '../components/Footer'
Expand All @@ -16,8 +17,9 @@ export default function RootLayout({
children: React.ReactNode
}>) {
return (
<html lang="en">
<html lang="en" suppressHydrationWarning>
<head>
<ThemeHead auto />
<base
href={process.env.NODE_ENV === 'production' ? '/devup-ui' : '/'}
/>
Expand Down
2 changes: 0 additions & 2 deletions apps/landing/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ import Link from 'next/link'
import { CodeBoard } from '../components/CodeBoard'
import { Container } from '../components/Container'
import { Discord } from '../components/Discord'
import { Header } from '../components/Header'
import { URL_PREFIX } from '../constants'

export default function HomePage() {
return (
<>
<Header />
<Box mt="150px">
<VStack alignItems="center" gap="50px" maxW="800px" mx="auto">
<VStack alignItems="center" gap="24px">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,36 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
'use client'

import { Box, getTheme, setTheme } from '@devup-ui/react'

export function ThemeSwitch() {
return (
<Box
_themeDark={{
color: 'white',
}}
_themeDefault={{
color: 'black',
}}
boxSize="24px"
cursor="pointer"
onClick={() => {
setTheme(getTheme() === 'dark' ? 'default' : 'dark')
}}
>
<svg
fill="none"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
clipRule="evenodd"
d="M13 5C13 5.55228 12.5523 6 12 6C11.4477 6 11 5.55228 11 5C11 4.44772 11.4477 4 12 4C12.5523 4 13 4.44772 13 5ZM17 8C17.5523 8 18 7.55228 18 7C18 6.44772 17.5523 6 17 6C16.4477 6 16 6.44772 16 7C16 7.55228 16.4477 8 17 8ZM17 12C17 14.7614 14.7614 17 12 17C9.23858 17 7 14.7614 7 12C7 9.23858 9.23858 7 12 7C14.7614 7 17 9.23858 17 12ZM13 19C13 19.5523 12.5523 20 12 20C11.4477 20 11 19.5523 11 19C11 18.4477 11.4477 18 12 18C12.5523 18 13 18.4477 13 19ZM17 18C17.5523 18 18 17.5523 18 17C18 16.4477 17.5523 16 17 16C16.4477 16 16 16.4477 16 17C16 17.5523 16.4477 18 17 18ZM20 12C20 12.5523 19.5523 13 19 13C18.4477 13 18 12.5523 18 12C18 11.4477 18.4477 11 19 11C19.5523 11 20 11.4477 20 12ZM7 8C7.55228 8 8 7.55228 8 7C8 6.44772 7.55228 6 7 6C6.44772 6 6 6.44772 6 7C6 7.55228 6.44772 8 7 8ZM6 12C6 12.5523 5.55228 13 5 13C4.44772 13 4 12.5523 4 12C4 11.4477 4.44772 11 5 11C5.55228 11 6 11.4477 6 12ZM7 18C7.55228 18 8 17.5523 8 17C8 16.4477 7.55228 16 7 16C6.44772 16 6 16.4477 6 17C6 17.5523 6.44772 18 7 18Z"
fill="#1A1A1A"/>
</svg>
fill="currentColor"
fillRule="evenodd"
/>
</svg>
</Box>
)
}
3 changes: 2 additions & 1 deletion apps/landing/src/components/Header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Link from 'next/link'

import { URL_PREFIX } from '../../constants'
import { HeaderWrap } from './HeaderWrap'
import { ThemeSwitch } from './ThemeSwitch'

export function Header() {
return (
Expand Down Expand Up @@ -66,7 +67,7 @@ export function Header() {
</Link>
</Flex>
<Flex alignItems="center" px="10px">
<Image boxSize="24px" src={URL_PREFIX + '/light.svg'} />
<ThemeSwitch />
</Flex>
</Flex>
</Flex>
Expand Down
2 changes: 1 addition & 1 deletion bindings/devup-ui-wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ js-sys = "0.3.76"

[dev-dependencies]
wasm-bindgen-test = "0.3.50"

serial_test = "3.2.0"
112 changes: 102 additions & 10 deletions bindings/devup-ui-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,23 +113,33 @@ pub fn code_extract(
pub fn object_to_typography(obj: Object, level: u8) -> Result<Typography, JsValue> {
Ok(Typography::new(
Reflect::get(&obj, &JsValue::from_str("fontFamily"))
.map(|v| v.as_string())
.as_ref()
.map(js_value_to_string)
.unwrap_or(None),
Reflect::get(&obj, &JsValue::from_str("fontSize"))
.map(|v| v.as_string())
.as_ref()
.map(js_value_to_string)
.unwrap_or(None),
Reflect::get(&obj, &JsValue::from_str("fontWeight"))
.map(|v| v.as_string())
.as_ref()
.map(js_value_to_string)
.unwrap_or(None),
Reflect::get(&obj, &JsValue::from_str("lineHeight"))
.map(|v| v.as_string())
.as_ref()
.map(js_value_to_string)
.unwrap_or(None),
Reflect::get(&obj, &JsValue::from_str("letterSpacing"))
.map(|v| v.as_string())
.as_ref()
.map(js_value_to_string)
.unwrap_or(None),
level,
))
}
pub fn js_value_to_string(js_value: &JsValue) -> Option<String> {
js_value
.as_string()
.or_else(|| js_value.as_f64().map(|v| v.to_string()))
}

fn theme_object_to_hashmap(js_value: JsValue) -> Result<Theme, JsValue> {
let mut theme = Theme::default();
Expand Down Expand Up @@ -229,10 +239,12 @@ pub fn get_theme_interface(
package_name: &str,
color_interface_name: &str,
typography_interface_name: &str,
) -> Result<String, JsValue> {
theme_interface_name: &str,
) -> String {
let sheet = GLOBAL_STYLE_SHEET.lock().unwrap();
let mut color_keys = HashSet::new();
let mut typography_keys = HashSet::new();
let mut theme_keys = HashSet::new();
for color_theme in sheet.theme.colors.themes.values() {
color_theme.keys().for_each(|key| {
color_keys.insert(key.clone());
Expand All @@ -242,11 +254,15 @@ pub fn get_theme_interface(
typography_keys.insert(key.clone());
});

sheet.theme.colors.themes.keys().for_each(|key| {
theme_keys.insert(key.clone());
});

if color_keys.is_empty() && typography_keys.is_empty() {
Ok("".to_string())
String::new()
} else {
Ok(format!(
"import \"{}\";declare module \"{}\"{{interface {} {{{}}}interface {} {{{}}}}}",
format!(
"import \"{}\";declare module \"{}\"{{interface {}{{{}}}interface {}{{{}}}interface {}{{{}}}}}",
package_name,
package_name,
color_interface_name,
Expand All @@ -260,7 +276,83 @@ pub fn get_theme_interface(
.into_iter()
.map(|key| format!("{}:null;", key))
.collect::<Vec<String>>()
.join(""),
theme_interface_name,
theme_keys
.into_iter()
// key to pascal
.map(|key| format!("{}:null;", key))
.collect::<Vec<String>>()
.join("")
))
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serial_test::serial;

#[test]
#[serial]
fn test_code_extract() {
{
let mut sheet = GLOBAL_STYLE_SHEET.lock().unwrap();
*sheet = StyleSheet::default();
}
assert_eq!(get_css().unwrap(), "");

{
let mut sheet = GLOBAL_STYLE_SHEET.lock().unwrap();
let mut theme = Theme::default();
let mut color_theme = ColorTheme::default();
color_theme.add_color("primary", "#000");
theme.colors.add_theme("dark", color_theme);

let mut color_theme = ColorTheme::default();
color_theme.add_color("primary", "#FFF");
theme.colors.add_theme("default", color_theme);
sheet.set_theme(theme);
}

assert_eq!(
get_css().unwrap(),
":root{--primary:#FFF;}\n:root[data-theme=dark]{--primary:#000;}\n"
);
}

#[test]
#[serial]
fn test_get_theme_interface() {
{
let mut sheet = GLOBAL_STYLE_SHEET.lock().unwrap();
*sheet = StyleSheet::default();
}
assert_eq!(
get_theme_interface(
"package",
"ColorInterface",
"TypographyInterface",
"ThemeInterface"
),
""
);

{
let mut sheet = GLOBAL_STYLE_SHEET.lock().unwrap();
let mut theme = Theme::default();
let mut color_theme = ColorTheme::default();
color_theme.add_color("primary", "#000");
theme.colors.add_theme("dark", color_theme);
sheet.set_theme(theme);
}
assert_eq!(
get_theme_interface(
"package",
"ColorInterface",
"TypographyInterface",
"ThemeInterface"
),
"import \"package\";declare module \"package\"{interface ColorInterface{$primary:null;}interface TypographyInterface{}interface ThemeInterface{dark:null;}}"
);
}
}
58 changes: 47 additions & 11 deletions libs/css/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::fmt;
use std::fmt::{Display, Formatter};
use std::sync::Mutex;

#[derive(Debug, PartialEq, Clone, Hash, Eq)]
#[derive(Debug, PartialEq, Clone, Hash, Eq, Ord, PartialOrd)]
pub enum StyleSelector {
Postfix(String),
Prefix(String),
Expand All @@ -16,6 +16,13 @@ impl From<&str> for StyleSelector {
fn from(value: &str) -> Self {
if let Some(s) = value.strip_prefix("group") {
Dual("*[role=group]".to_string(), to_kebab_case(s))
} else if let Some(s) = value.strip_prefix("theme") {
// first character should lower case
Prefix(format!(
":root[data-theme={}{}]",
s.chars().next().unwrap().to_ascii_lowercase(),
&s[1..]
))
} else {
Postfix(value.to_string())
}
Expand Down Expand Up @@ -43,7 +50,7 @@ pub fn merge_selector(class_name: &str, selector: Option<&StyleSelector>) -> Str
SelectorSeparator::Single => format!(".{}:{}", class_name, postfix),
SelectorSeparator::Double => format!(".{}::{}", class_name, postfix),
},
Prefix(prefix) => format!("{} {}", prefix, class_name),
Prefix(prefix) => format!("{} .{}", prefix, class_name),
Dual(prefix, postfix) => match get_selector_separator(postfix) {
SelectorSeparator::Single => format!("{}:{} .{}", prefix, postfix, class_name),
SelectorSeparator::Double => format!("{}::{} .{}", prefix, postfix, class_name),
Expand All @@ -59,15 +66,6 @@ pub enum SelectorSeparator {
Double,
}

impl SelectorSeparator {
pub fn separator(&self) -> &str {
match self {
SelectorSeparator::Single => ":",
SelectorSeparator::Double => "::",
}
}
}

static DOUBLE_SEPARATOR: Lazy<HashSet<&str>> = Lazy::new(|| {
let mut set = HashSet::new();

Expand Down Expand Up @@ -534,5 +532,43 @@ mod tests {

assert_eq!(Prefix(".cls".to_string()).to_string(), "-.cls-");
assert_eq!(Postfix(".cls".to_string()).to_string(), "-.cls");

assert_eq!(
StyleSelector::from("themeLight"),
Prefix(":root[data-theme=light]".to_string())
);
}

#[test]
fn test_merge_selector() {
assert_eq!(merge_selector("cls", Some(&"hover".into())), ".cls:hover");
assert_eq!(
merge_selector("cls", Some(&"placeholder".into())),
".cls::placeholder"
);
assert_eq!(
merge_selector("cls", Some(&"themeDark".into())),
":root[data-theme=dark] .cls"
);
assert_eq!(
merge_selector(
"cls",
Some(&Dual(
":root[data-theme=dark]".to_string(),
"hover".to_string()
)),
),
":root[data-theme=dark]:hover .cls"
);
assert_eq!(
merge_selector(
"cls",
Some(&Dual(
":root[data-theme=dark]".to_string(),
"placeholder".to_string()
)),
),
":root[data-theme=dark]::placeholder .cls"
);
}
}
Loading
Loading