Skip to content

Commit 946dbc2

Browse files
committed
Impl keyframe
1 parent 2fa59bc commit 946dbc2

38 files changed

+1354
-52
lines changed

.changeset/yummy-brooms-appear.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@devup-ui/wasm": patch
3+
---
4+
5+
Implement keyframe

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/landing/src/app/StarButton.css

Lines changed: 0 additions & 9 deletions
This file was deleted.

apps/landing/src/app/StarButton.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
'use client'
22

3-
import './StarButton.css'
4-
5-
import { Center, css, Flex, Image, Text } from '@devup-ui/react'
3+
import { Center, css, Flex, Image, keyframes, Text } from '@devup-ui/react'
64
import Link from 'next/link'
75
import { useEffect, useState } from 'react'
86

7+
const spin = keyframes({
8+
'0%': {
9+
transform: 'rotate(0deg)',
10+
},
11+
'100%': {
12+
transform: 'rotate(360deg)',
13+
},
14+
})
15+
916
export default function StarButton() {
1017
const [starCount, setStarCount] = useState<number | null>(null)
1118

@@ -88,7 +95,8 @@ export default function StarButton() {
8895
{starCount === null ? (
8996
<Image
9097
alt="Loading"
91-
animation="spin 1s linear infinite"
98+
animation="1s linear infinite"
99+
animationName={spin}
92100
boxSize="20px"
93101
src="/spinner.svg"
94102
/>

bindings/devup-ui-wasm/src/lib.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use css::class_map::{get_class_map, set_class_map};
2+
use extractor::extract_style::ExtractStyleProperty;
23
use extractor::extract_style::extract_style_value::ExtractStyleValue;
3-
use extractor::{ExtractOption, StyleProperty, extract};
4+
use extractor::extract_style::style_property::StyleProperty;
5+
use extractor::{ExtractOption, extract};
46
use once_cell::sync::Lazy;
57
use sheet::StyleSheet;
68
use std::collections::HashSet;
@@ -90,6 +92,32 @@ impl Output {
9092
collected = true;
9193
}
9294
}
95+
96+
ExtractStyleValue::Keyframes(keyframes) => {
97+
if sheet.add_keyframes(
98+
&keyframes.extract().to_string(),
99+
keyframes
100+
.keyframes
101+
.iter()
102+
.map(|(key, value)| {
103+
(
104+
key.clone(),
105+
value
106+
.iter()
107+
.map(|style| {
108+
(
109+
style.property().to_string(),
110+
style.value().to_string(),
111+
)
112+
})
113+
.collect::<Vec<(String, String)>>(),
114+
)
115+
})
116+
.collect(),
117+
) {
118+
collected = true;
119+
}
120+
}
93121
ExtractStyleValue::Css(cs) => {
94122
if sheet.add_css(&cs.file, &cs.css) {
95123
collected = true;

libs/css/src/class_map.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,27 @@ pub fn set_class_map(map: HashMap<String, i32>) {
1919
pub fn get_class_map() -> HashMap<String, i32> {
2020
GLOBAL_CLASS_MAP.lock().unwrap().clone()
2121
}
22+
23+
#[cfg(test)]
24+
mod tests {
25+
use super::*;
26+
27+
#[test]
28+
fn test_set_and_get_class_map() {
29+
let mut test_map = HashMap::new();
30+
test_map.insert("test-key".to_string(), 42);
31+
set_class_map(test_map.clone());
32+
let got = get_class_map();
33+
assert_eq!(got.get("test-key"), Some(&42));
34+
}
35+
36+
#[test]
37+
fn test_reset_class_map() {
38+
let mut test_map = HashMap::new();
39+
test_map.insert("reset-key".to_string(), 1);
40+
set_class_map(test_map);
41+
reset_class_map();
42+
let got = get_class_map();
43+
assert!(got.is_empty());
44+
}
45+
}

libs/css/src/lib.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,20 @@ pub fn short_to_long(property: &str) -> String {
3838
.unwrap_or_else(|| property.to_string())
3939
}
4040

41+
pub fn keyframes_to_keyframes_name(keyframes: &str) -> String {
42+
if is_debug() {
43+
format!("k-{keyframes}")
44+
} else {
45+
let key = format!("k-{keyframes}");
46+
let mut map = GLOBAL_CLASS_MAP.lock().unwrap();
47+
map.get(&key).map(|v| format!("k{v}")).unwrap_or_else(|| {
48+
let len = map.len();
49+
map.insert(key, len as i32);
50+
format!("k{}", map.len() - 1)
51+
})
52+
}
53+
}
54+
4155
pub fn sheet_to_classname(
4256
property: &str,
4357
level: u8,
@@ -419,4 +433,18 @@ mod tests {
419433
set_class_map(map);
420434
assert_eq!(get_class_map().len(), 1);
421435
}
436+
437+
#[test]
438+
#[serial]
439+
fn test_keyframes_to_keyframes_name() {
440+
reset_class_map();
441+
set_debug(false);
442+
assert_eq!(keyframes_to_keyframes_name("spin"), "k0");
443+
assert_eq!(keyframes_to_keyframes_name("spin"), "k0");
444+
assert_eq!(keyframes_to_keyframes_name("spin2"), "k1");
445+
reset_class_map();
446+
set_debug(true);
447+
assert_eq!(keyframes_to_keyframes_name("spin"), "k-spin");
448+
assert_eq!(keyframes_to_keyframes_name("spin1"), "k-spin1");
449+
}
422450
}

libs/extractor/src/css_utils.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::collections::BTreeMap;
2+
13
use css::{style_selector::StyleSelector, utils::to_camel_case};
24

35
use crate::{
@@ -30,6 +32,37 @@ pub fn css_to_style<'a>(
3032
.collect()
3133
}
3234

35+
pub fn keyframes_to_keyframes_style<'a>(
36+
keyframes: &str,
37+
) -> BTreeMap<String, Vec<ExtractStyleProp<'a>>> {
38+
let mut map = BTreeMap::new();
39+
let mut input = keyframes;
40+
41+
while let Some(start) = input.find('{') {
42+
let key = input[..start].trim().to_string();
43+
let rest = &input[start + 1..];
44+
if let Some(end) = rest.find('}') {
45+
let block = &rest[..end];
46+
let mut styles = css_to_style(block, 0, &None)
47+
.into_iter()
48+
.collect::<Vec<_>>();
49+
50+
styles.sort_by_key(|a| {
51+
if let crate::ExtractStyleProp::Static(crate::ExtractStyleValue::Static(a)) = a {
52+
a.property().to_string()
53+
} else {
54+
"".to_string()
55+
}
56+
});
57+
map.insert(key, styles);
58+
input = &rest[end + 1..];
59+
} else {
60+
break;
61+
}
62+
}
63+
map
64+
}
65+
3366
pub fn optimize_css_block(css: &str) -> String {
3467
css.split("{")
3568
.map(|s| s.trim().to_string())
@@ -143,4 +176,72 @@ mod tests {
143176
expected_sorted.sort();
144177
assert_eq!(result, expected_sorted);
145178
}
179+
180+
#[rstest]
181+
#[case(
182+
"to {\nbackground-color:red;\n}\nfrom {\nbackground-color:blue;\n}",
183+
vec![
184+
("to", vec![("backgroundColor", "red")]),
185+
("from", vec![("backgroundColor", "blue")]),
186+
],
187+
)]
188+
#[case(
189+
"0% { opacity: 0; }\n100% { opacity: 1; }",
190+
vec![
191+
("0%", vec![("opacity", "0")]),
192+
("100%", vec![("opacity", "1")]),
193+
],
194+
)]
195+
#[case(
196+
"from { left: 0; }\nto { left: 100px; }",
197+
vec![
198+
("from", vec![("left", "0")]),
199+
("to", vec![("left", "100px")]),
200+
],
201+
)]
202+
#[case(
203+
"50% { color: red; background: blue; }",
204+
vec![
205+
("50%", vec![("color", "red"), ("background", "blue")]),
206+
],
207+
)]
208+
#[case(
209+
"",
210+
vec![],
211+
)]
212+
#[case(
213+
"50% { color: red ; background: blue; }",
214+
vec![
215+
("50%", vec![("color", "red"), ("background", "blue")]),
216+
],
217+
)]
218+
fn test_keyframes_to_keyframes_style(
219+
#[case] input: &str,
220+
#[case] expected: Vec<(&str, Vec<(&str, &str)>)>,
221+
) {
222+
let styles = keyframes_to_keyframes_style(input);
223+
for (expected_key, expected_styles) in styles.iter() {
224+
let styles = expected_styles;
225+
let mut result: Vec<(&str, &str)> = styles
226+
.iter()
227+
.filter_map(|prop| {
228+
if let crate::ExtractStyleProp::Static(crate::ExtractStyleValue::Static(st)) =
229+
prop
230+
{
231+
Some((st.property(), st.value()))
232+
} else {
233+
None
234+
}
235+
})
236+
.collect();
237+
result.sort();
238+
let mut expected_sorted = expected
239+
.iter()
240+
.find(|(k, _)| k == expected_key)
241+
.map(|(_, v)| v.clone())
242+
.unwrap();
243+
expected_sorted.sort();
244+
assert_eq!(result, expected_sorted);
245+
}
246+
}
146247
}

libs/extractor/src/extract_style/extract_dynamic_style.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use css::{
33
style_selector::StyleSelector,
44
};
55

6-
use crate::{StyleProperty, extract_style::ExtractStyleProperty};
6+
use crate::extract_style::{ExtractStyleProperty, style_property::StyleProperty};
77

88
#[derive(Debug, PartialEq, Clone, Eq, Hash, Ord, PartialOrd)]
99
pub struct ExtractDynamicStyle {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
use std::{
2+
collections::BTreeMap,
3+
hash::{DefaultHasher, Hash, Hasher},
4+
};
5+
6+
use css::keyframes_to_keyframes_name;
7+
8+
use crate::extract_style::{
9+
ExtractStyleProperty, extract_static_style::ExtractStaticStyle, style_property::StyleProperty,
10+
};
11+
12+
#[derive(Debug, Default, PartialEq, Clone, Eq, Hash, Ord, PartialOrd)]
13+
pub struct ExtractKeyframes {
14+
pub keyframes: BTreeMap<String, Vec<ExtractStaticStyle>>,
15+
}
16+
17+
impl ExtractStyleProperty for ExtractKeyframes {
18+
fn extract(&self) -> StyleProperty {
19+
let mut hasher = DefaultHasher::new();
20+
self.keyframes.hash(&mut hasher);
21+
let hash_key = hasher.finish().to_string();
22+
StyleProperty::ClassName(keyframes_to_keyframes_name(&hash_key))
23+
}
24+
}

0 commit comments

Comments
 (0)