Skip to content

Commit bff545d

Browse files
committed
feat: expand styles prop into className and style props with optional handling
1 parent 61fc97f commit bff545d

File tree

11 files changed

+444
-12
lines changed

11 files changed

+444
-12
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
(library
2+
(name expand_styles_attribute)
3+
(public_name server-reason-react.expand-styles-attribute)
4+
(libraries ppxlib ppxlib.astlib)
5+
(preprocess
6+
(pps ppxlib.metaquot)))
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
let make ~loc attributes =
2+
let merge_className current_className (label, expr) =
3+
match current_className with
4+
| Some (existing_label, existing_expr) ->
5+
let merged =
6+
match label with
7+
| Ppxlib.Optional "className" ->
8+
[%expr match [%e expr] with None -> [%e existing_expr] | Some x -> x ^ " " ^ [%e existing_expr]]
9+
| _ -> [%expr [%e expr] ^ " " ^ [%e existing_expr]]
10+
in
11+
Some (existing_label, merged)
12+
| None -> Some (label, expr)
13+
in
14+
let merge_style current_style (label, expr) =
15+
match current_style with
16+
| Some (existing_label, existing_expr) ->
17+
let merged =
18+
match label with
19+
| Ppxlib.Optional "style" ->
20+
[%expr
21+
match [%e expr] with
22+
| None -> [%e existing_expr]
23+
| Some x -> ReactDOM.Style.combine [%e existing_expr] x]
24+
| _ -> [%expr ReactDOM.Style.combine [%e existing_expr] [%e expr]]
25+
in
26+
Some (existing_label, merged)
27+
| None -> Some (label, expr)
28+
in
29+
let handle_styles className style label arg =
30+
let className_label, className_expr, style_label, style_expr =
31+
match label with
32+
| Ppxlib.Labelled "styles" ->
33+
(Ppxlib.Labelled "className", [%expr fst [%e arg]], Ppxlib.Labelled "style", [%expr snd [%e arg]])
34+
| _ ->
35+
( Ppxlib.Optional "className",
36+
[%expr match [%e arg] with None -> None | Some x -> Some (fst x)],
37+
Ppxlib.Optional "style",
38+
[%expr match [%e arg] with None -> None | Some x -> Some (snd x)] )
39+
in
40+
(merge_className className (className_label, className_expr), merge_style style (style_label, style_expr))
41+
in
42+
let rec aux (className, style, other_args) args =
43+
match args with
44+
| [] ->
45+
let rest = List.rev other_args in
46+
([ className; style ] |> List.filter_map Stdlib.Fun.id) @ rest
47+
| (label, arg) :: rest -> (
48+
match label with
49+
| Ppxlib.Labelled "className" | Ppxlib.Optional "className" ->
50+
aux (merge_className className (label, arg), style, other_args) rest
51+
| Ppxlib.Labelled "style" | Ppxlib.Optional "style" ->
52+
aux (className, merge_style style (label, arg), other_args) rest
53+
| Ppxlib.Labelled "styles" | Ppxlib.Optional "styles" ->
54+
let new_className, new_style = handle_styles className style label arg in
55+
aux (new_className, new_style, other_args) rest
56+
| _ -> aux (className, style, (label, arg) :: other_args) rest)
57+
in
58+
aux (None, None, []) attributes
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
(test
2+
(name test)
3+
(libraries
4+
ppxlib
5+
ppxlib.astlib
6+
alcotest
7+
server-reason-react.expand-styles-attribute)
8+
(preprocess
9+
(pps ppxlib.metaquot)))
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
let test_expand_styles () =
2+
let loc = Ppxlib.Location.none in
3+
let expr = [%expr "some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ()] in
4+
let attributes = [ (Ppxlib.Labelled "styles", expr) ] in
5+
let expanded_attributes = Expand_styles_attribute.make ~loc:Ppxlib.Location.none attributes in
6+
7+
List.iter
8+
(fun attribute ->
9+
match attribute with
10+
| ( Ppxlib.Labelled "className",
11+
[%expr fst ("some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ())] ) ->
12+
Alcotest.(check pass) "className uses fst" () ()
13+
| Ppxlib.Labelled "style", [%expr snd ("some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ())] ->
14+
Alcotest.(check pass) "style uses snd" () ()
15+
| _ -> Alcotest.fail "Expanded attributes should be className and style")
16+
expanded_attributes
17+
18+
let test_expand_styles_with_previous_className () =
19+
let loc = Ppxlib.Location.none in
20+
let expr = [%expr "some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ()] in
21+
let attributes = [ (Ppxlib.Labelled "className", [%expr "previous-class-name"]); (Ppxlib.Labelled "styles", expr) ] in
22+
let expanded_attributes = Expand_styles_attribute.make ~loc:Ppxlib.Location.none attributes in
23+
List.iter
24+
(fun attribute ->
25+
match attribute with
26+
| ( Ppxlib.Labelled "className",
27+
[%expr
28+
fst ("some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ()) ^ " " ^ "previous-class-name"]
29+
) ->
30+
Alcotest.(check pass) "className uses previous class name" () ()
31+
| Ppxlib.Labelled "style", [%expr snd ("some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ())] ->
32+
Alcotest.(check pass) "style uses combine" () ()
33+
| _ -> Alcotest.fail "Expanded attributes should be className and style")
34+
expanded_attributes
35+
36+
let test_expand_styles_with_previous_styles () =
37+
let loc = Ppxlib.Location.none in
38+
let expr = [%expr "some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ()] in
39+
let attributes = [ (Ppxlib.Labelled "style", [%expr "previous-styles"]); (Ppxlib.Labelled "styles", expr) ] in
40+
let expanded_attributes = Expand_styles_attribute.make ~loc:Ppxlib.Location.none attributes in
41+
List.iter
42+
(fun attribute ->
43+
match attribute with
44+
| ( Ppxlib.Labelled "className",
45+
[%expr fst ("some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ())] ) ->
46+
Alcotest.(check pass) "className uses fst" () ()
47+
| ( Ppxlib.Labelled "style",
48+
[%expr
49+
ReactDOM.Style.combine "previous-styles"
50+
(snd ("some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ()))] ) ->
51+
Alcotest.(check pass) "style uses combine" () ()
52+
| _ -> Alcotest.fail "Expanded attributes should be className and style")
53+
expanded_attributes
54+
55+
let test_expand_styles_optional () =
56+
let loc = Ppxlib.Location.none in
57+
let expr = [%expr Some ("some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ())] in
58+
let attributes = [ (Ppxlib.Optional "styles", expr) ] in
59+
let expanded_attributes = Expand_styles_attribute.make ~loc:Ppxlib.Location.none attributes in
60+
List.iter
61+
(fun attribute ->
62+
match attribute with
63+
| ( Ppxlib.Optional "className",
64+
[%expr
65+
match Some ("some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ()) with
66+
| None -> None
67+
| Some x -> Some (fst x)] ) ->
68+
Alcotest.(check pass) "className uses fst" () ()
69+
| ( Ppxlib.Optional "style",
70+
[%expr
71+
match Some ("some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ()) with
72+
| None -> None
73+
| Some x -> Some (snd x)] ) ->
74+
Alcotest.(check pass) "style uses snd" () ()
75+
| _ -> Alcotest.fail "Expanded attributes should be className and style")
76+
expanded_attributes
77+
78+
let test_expand_styles_optional_with_previous_className () =
79+
let loc = Ppxlib.Location.none in
80+
let expr = [%expr Some ("some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ())] in
81+
let attributes = [ (Ppxlib.Labelled "className", [%expr "previous-class-name"]); (Ppxlib.Optional "styles", expr) ] in
82+
let expanded_attributes = Expand_styles_attribute.make ~loc:Ppxlib.Location.none attributes in
83+
List.iter
84+
(fun attribute ->
85+
match attribute with
86+
| ( Ppxlib.Labelled "className",
87+
[%expr
88+
match
89+
match Some ("some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ()) with
90+
| None -> None
91+
| Some x -> Some (fst x)
92+
with
93+
| None -> "previous-class-name"
94+
| Some x -> x ^ " " ^ "previous-class-name"] ) ->
95+
Alcotest.(check pass) "className uses fst" () ()
96+
| ( Ppxlib.Optional "style",
97+
[%expr
98+
match Some ("some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ()) with
99+
| None -> None
100+
| Some x -> Some (snd x)] ) ->
101+
Alcotest.(check pass) "style uses snd" () ()
102+
| _ -> Alcotest.fail "Expanded attributes should be className and style")
103+
expanded_attributes
104+
105+
let test_expand_styles_optional_with_previous_styles () =
106+
let loc = Ppxlib.Location.none in
107+
let expr = [%expr Some ("some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ())] in
108+
let attributes = [ (Ppxlib.Labelled "style", [%expr "previous-styles"]); (Ppxlib.Optional "styles", expr) ] in
109+
let expanded_attributes = Expand_styles_attribute.make ~loc:Ppxlib.Location.none attributes in
110+
List.iter
111+
(fun attribute ->
112+
match attribute with
113+
| ( Ppxlib.Optional "className",
114+
[%expr
115+
match Some ("some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ()) with
116+
| None -> None
117+
| Some x -> Some (fst x)] ) ->
118+
Alcotest.(check pass) "className uses fst" () ()
119+
| ( Ppxlib.Labelled "style",
120+
[%expr
121+
match
122+
match Some ("some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ()) with
123+
| None -> None
124+
| Some x -> Some (snd x)
125+
with
126+
| None -> "previous-styles"
127+
| Some x -> ReactDOM.Style.combine "previous-styles" x] ) ->
128+
Alcotest.(check pass) "style uses snd" () ()
129+
| _ -> Alcotest.fail "Expanded attributes should be className and style")
130+
expanded_attributes
131+
132+
let test title fn = (title, [ Alcotest.test_case "" `Quick fn ])
133+
134+
let () =
135+
Alcotest.run "expand_styles_attribute"
136+
[
137+
test "expand_styles_prop_on_attributes" test_expand_styles;
138+
test "expand_styles_with_previous_className" test_expand_styles_with_previous_className;
139+
test "expand_styles_with_previous_styles" test_expand_styles_with_previous_styles;
140+
test "expand_styles_optional" test_expand_styles_optional;
141+
test "expand_styles_optional_with_previous_className" test_expand_styles_optional_with_previous_className;
142+
test "expand_styles_optional_with_previous_styles" test_expand_styles_optional_with_previous_styles;
143+
]

packages/server-reason-react-ppx/cram/component-definition.t/run.t

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ We need to output ML syntax here, otherwise refmt could not parse it.
1919
React.createElement "button"
2020
(Stdlib.List.filter_map Stdlib.Fun.id
2121
[
22-
Some (React.JSX.Ref (buttonRef : React.domRef));
2322
Some
2423
(React.JSX.String
2524
("class", "className", ("FancyButton" : string)));
25+
Some (React.JSX.Ref (buttonRef : React.domRef));
2626
])
2727
[ children ] )
2828
end

packages/server-reason-react-ppx/cram/lower-calls.t/run.t

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,11 +209,11 @@
209209
Stdlib.List.filter_map(
210210
Stdlib.Fun.id,
211211
[
212-
Some(React.JSX.Ref(ref: React.domRef)),
213212
Some(
214213
[@implicit_arity]
215214
React.JSX.String("class", "className", "FancyButton": string),
216215
),
216+
Some(React.JSX.Ref(ref: React.domRef)),
217217
],
218218
),
219219
[children],
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,9 @@
1+
12
<div styles=x />;
3+
<div styles=?x />;
4+
<div className="lola" styles=x />;
5+
<div style={ReactDOM.Style.make(~backgroundColor="gainsboro", ())} styles=x />;
6+
<div className="lola" style={ReactDOM.Style.make(~backgroundColor="gainsboro", ())} styles=x />;
7+
<div className="lola" styles=?x />;
8+
<div style={ReactDOM.Style.make(~backgroundColor="gainsboro", ())} styles=?x />;
9+
<div className="lola" style={ReactDOM.Style.make(~backgroundColor="gainsboro", ())} styles=?x />;

packages/server-reason-react-ppx/cram/styles.t/run.t

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,128 @@ We need to output ML syntax here, otherwise refmt could not parse it.
88
Some (React.JSX.Style (snd x : ReactDOM.Style.t));
99
])
1010
[]
11+
;;
12+
13+
React.createElement "div"
14+
(Stdlib.List.filter_map Stdlib.Fun.id
15+
[
16+
(match
17+
(match x with None -> None | Some x -> Some (fst x) : string option)
18+
with
19+
| None -> None
20+
| Some v -> Some (React.JSX.String ("class", "className", v)));
21+
(match
22+
(match x with None -> None | Some x -> Some (snd x)
23+
: ReactDOM.Style.t option)
24+
with
25+
| None -> None
26+
| Some v -> Some (React.JSX.Style v));
27+
])
28+
[]
29+
;;
30+
31+
React.createElement "div"
32+
(Stdlib.List.filter_map Stdlib.Fun.id
33+
[
34+
Some
35+
(React.JSX.String
36+
("class", "className", (fst x ^ " " ^ "lola" : string)));
37+
Some (React.JSX.Style (snd x : ReactDOM.Style.t));
38+
])
39+
[]
40+
;;
41+
42+
React.createElement "div"
43+
(Stdlib.List.filter_map Stdlib.Fun.id
44+
[
45+
Some (React.JSX.String ("class", "className", (fst x : string)));
46+
Some
47+
(React.JSX.Style
48+
(ReactDOM.Style.combine
49+
(ReactDOM.Style.make ~backgroundColor:"gainsboro" ())
50+
(snd x)
51+
: ReactDOM.Style.t));
52+
])
53+
[]
54+
;;
55+
56+
React.createElement "div"
57+
(Stdlib.List.filter_map Stdlib.Fun.id
58+
[
59+
Some
60+
(React.JSX.String
61+
("class", "className", (fst x ^ " " ^ "lola" : string)));
62+
Some
63+
(React.JSX.Style
64+
(ReactDOM.Style.combine
65+
(ReactDOM.Style.make ~backgroundColor:"gainsboro" ())
66+
(snd x)
67+
: ReactDOM.Style.t));
68+
])
69+
[]
70+
;;
71+
72+
React.createElement "div"
73+
(Stdlib.List.filter_map Stdlib.Fun.id
74+
[
75+
Some
76+
(React.JSX.String
77+
( "class",
78+
"className",
79+
(match match x with None -> None | Some x -> Some (fst x) with
80+
| None -> "lola"
81+
| Some x -> x ^ " " ^ "lola"
82+
: string) ));
83+
(match
84+
(match x with None -> None | Some x -> Some (snd x)
85+
: ReactDOM.Style.t option)
86+
with
87+
| None -> None
88+
| Some v -> Some (React.JSX.Style v));
89+
])
90+
[]
91+
;;
92+
93+
React.createElement "div"
94+
(Stdlib.List.filter_map Stdlib.Fun.id
95+
[
96+
(match
97+
(match x with None -> None | Some x -> Some (fst x) : string option)
98+
with
99+
| None -> None
100+
| Some v -> Some (React.JSX.String ("class", "className", v)));
101+
Some
102+
(React.JSX.Style
103+
(match match x with None -> None | Some x -> Some (snd x) with
104+
| None -> ReactDOM.Style.make ~backgroundColor:"gainsboro" ()
105+
| Some x ->
106+
ReactDOM.Style.combine
107+
(ReactDOM.Style.make ~backgroundColor:"gainsboro" ())
108+
x
109+
: ReactDOM.Style.t));
110+
])
111+
[]
112+
;;
113+
114+
React.createElement "div"
115+
(Stdlib.List.filter_map Stdlib.Fun.id
116+
[
117+
Some
118+
(React.JSX.String
119+
( "class",
120+
"className",
121+
(match match x with None -> None | Some x -> Some (fst x) with
122+
| None -> "lola"
123+
| Some x -> x ^ " " ^ "lola"
124+
: string) ));
125+
Some
126+
(React.JSX.Style
127+
(match match x with None -> None | Some x -> Some (snd x) with
128+
| None -> ReactDOM.Style.make ~backgroundColor:"gainsboro" ()
129+
| Some x ->
130+
ReactDOM.Style.combine
131+
(ReactDOM.Style.make ~backgroundColor:"gainsboro" ())
132+
x
133+
: ReactDOM.Style.t));
134+
])
135+
[]

packages/server-reason-react-ppx/dune

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
(kind ppx_rewriter)
1010
(libraries
1111
str
12+
server-reason-react.expand-styles-attribute
1213
compiler-libs.common
1314
ppxlib
1415
ppxlib.astlib

0 commit comments

Comments
 (0)