~tw: complete Tailwind implementation in pure SX, split into 3 files
- tw.sx: core engine (HSL color computation, token processor, responsive/ state prefixes) + visual styles (colors, borders, shadows, ring, opacity, transitions, animations, transforms, cursor, decoration) - tw-layout.sx: spatial arrangement (flex, grid, position, spacing, display, sizing, overflow, z-index, aspect-ratio, visibility) - tw-type.sx: typography (font size/weight/family, line-height, tracking, text alignment/transform/wrap, whitespace, word-break, truncate, lists, hyphens, font-variant-numeric, antialiasing) 22 color names × infinite shades via HSL computation (not lookup tables). Full responsive (sm/md/lg/xl/2xl) and state (hover/focus/active/disabled/ first/last/odd/even/visited/checked/before/after) prefix support. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
410
shared/sx/templates/tw-layout.sx
Normal file
410
shared/sx/templates/tw-layout.sx
Normal file
@@ -0,0 +1,410 @@
|
||||
(define tw-spacing-props {:ml "margin-left:{v}" :mr "margin-right:{v}" :mt "margin-top:{v}" :mb "margin-bottom:{v}" :pl "padding-left:{v}" :gap-y "row-gap:{v}" :m "margin:{v}" :gap-x "column-gap:{v}" :my "margin-top:{v};margin-bottom:{v}" :px "padding-left:{v};padding-right:{v}" :pb "padding-bottom:{v}" :pr "padding-right:{v}" :p "padding:{v}" :gap "gap:{v}" :py "padding-top:{v};padding-bottom:{v}" :pt "padding-top:{v}" :mx "margin-left:{v};margin-right:{v}"})
|
||||
|
||||
(define tw-displays {:flex "flex" :table "table" :grid "grid" :inline-block "inline-block" :table-row "table-row" :inline "inline" :hidden "none" :block "block" :contents "contents" :inline-flex "inline-flex" :inline-grid "inline-grid" :table-cell "table-cell"})
|
||||
|
||||
(define tw-max-widths {:xs "20rem" :3xl "48rem" :7xl "80rem" :sm "24rem" :xl "36rem" :full "100%" :md "28rem" :6xl "72rem" :prose "65ch" :max "max-content" :5xl "64rem" :min "min-content" :lg "32rem" :2xl "42rem" :4xl "56rem" :none "none" :screen "100vw" :fit "fit-content"})
|
||||
|
||||
(define tw-min-widths {:full "100%" :0 "0px" :max "max-content" :min "min-content" :fit "fit-content"})
|
||||
|
||||
(define
|
||||
tw-resolve-layout
|
||||
(fn
|
||||
(token)
|
||||
(let
|
||||
((parts (split token "-"))
|
||||
(head (first parts))
|
||||
(rest (slice parts 1)))
|
||||
(cond
|
||||
(and (= (len parts) 1) (not (nil? (get tw-displays head))))
|
||||
(str "display:" (get tw-displays head))
|
||||
(and (= (len parts) 2) (not (nil? (get tw-displays token))))
|
||||
(str "display:" (get tw-displays token))
|
||||
(and (get tw-spacing-props head) (= (len rest) 1))
|
||||
(let
|
||||
((tmpl (get tw-spacing-props head))
|
||||
(v (tw-spacing-value (first rest))))
|
||||
(if (nil? v) nil (tw-template tmpl v)))
|
||||
(and
|
||||
(= head "space")
|
||||
(= (len rest) 2)
|
||||
(or (= (first rest) "x") (= (first rest) "y")))
|
||||
(let
|
||||
((v (tw-spacing-value (nth rest 1))) (dir (first rest)))
|
||||
(if
|
||||
(nil? v)
|
||||
nil
|
||||
(if (= dir "x") (str "column-gap:" v) (str "row-gap:" v))))
|
||||
(and (= head "flex") (empty? rest))
|
||||
"display:flex"
|
||||
(and (= head "flex") (= (len rest) 1))
|
||||
(case
|
||||
(first rest)
|
||||
"row"
|
||||
"flex-direction:row"
|
||||
"col"
|
||||
"flex-direction:column"
|
||||
"wrap"
|
||||
"flex-wrap:wrap"
|
||||
"nowrap"
|
||||
"flex-wrap:nowrap"
|
||||
"1"
|
||||
"flex:1 1 0%"
|
||||
"auto"
|
||||
"flex:1 1 auto"
|
||||
"initial"
|
||||
"flex:0 1 auto"
|
||||
"none"
|
||||
"flex:none"
|
||||
:else nil)
|
||||
(and (= head "flex") (= (len rest) 2))
|
||||
(case
|
||||
(join "-" rest)
|
||||
"row-reverse"
|
||||
"flex-direction:row-reverse"
|
||||
"col-reverse"
|
||||
"flex-direction:column-reverse"
|
||||
"wrap-reverse"
|
||||
"flex-wrap:wrap-reverse"
|
||||
:else nil)
|
||||
(= head "grow")
|
||||
(if
|
||||
(empty? rest)
|
||||
"flex-grow:1"
|
||||
(if (= (first rest) "0") "flex-grow:0" nil))
|
||||
(= head "shrink")
|
||||
(if
|
||||
(empty? rest)
|
||||
"flex-shrink:1"
|
||||
(if (= (first rest) "0") "flex-shrink:0" nil))
|
||||
(and (= head "basis") (= (len rest) 1))
|
||||
(let
|
||||
((val (first rest)))
|
||||
(cond
|
||||
(= val "auto")
|
||||
"flex-basis:auto"
|
||||
(= val "full")
|
||||
"flex-basis:100%"
|
||||
(= val "0")
|
||||
"flex-basis:0px"
|
||||
(contains? val "/")
|
||||
(let
|
||||
((frac (split val "/")))
|
||||
(if
|
||||
(= (len frac) 2)
|
||||
(let
|
||||
((num (parse-int (first frac) nil))
|
||||
(den (parse-int (nth frac 1) nil)))
|
||||
(if
|
||||
(or (nil? num) (nil? den))
|
||||
nil
|
||||
(str "flex-basis:" (* (/ num den) 100) "%")))
|
||||
nil))
|
||||
:else (let
|
||||
((n (parse-int val nil)))
|
||||
(if (nil? n) nil (str "flex-basis:" (* n 0.25) "rem")))))
|
||||
(and (= head "justify") (= (len rest) 1))
|
||||
(case
|
||||
(first rest)
|
||||
"start"
|
||||
"justify-content:flex-start"
|
||||
"end"
|
||||
"justify-content:flex-end"
|
||||
"center"
|
||||
"justify-content:center"
|
||||
"between"
|
||||
"justify-content:space-between"
|
||||
"around"
|
||||
"justify-content:space-around"
|
||||
"evenly"
|
||||
"justify-content:space-evenly"
|
||||
"stretch"
|
||||
"justify-content:stretch"
|
||||
:else nil)
|
||||
(and (= head "items") (= (len rest) 1))
|
||||
(case
|
||||
(first rest)
|
||||
"start"
|
||||
"align-items:flex-start"
|
||||
"end"
|
||||
"align-items:flex-end"
|
||||
"center"
|
||||
"align-items:center"
|
||||
"baseline"
|
||||
"align-items:baseline"
|
||||
"stretch"
|
||||
"align-items:stretch"
|
||||
:else nil)
|
||||
(and (= head "self") (= (len rest) 1))
|
||||
(case
|
||||
(first rest)
|
||||
"auto"
|
||||
"align-self:auto"
|
||||
"start"
|
||||
"align-self:flex-start"
|
||||
"end"
|
||||
"align-self:flex-end"
|
||||
"center"
|
||||
"align-self:center"
|
||||
"stretch"
|
||||
"align-self:stretch"
|
||||
"baseline"
|
||||
"align-self:baseline"
|
||||
:else nil)
|
||||
(and (= head "content") (= (len rest) 1))
|
||||
(case
|
||||
(first rest)
|
||||
"start"
|
||||
"align-content:flex-start"
|
||||
"end"
|
||||
"align-content:flex-end"
|
||||
"center"
|
||||
"align-content:center"
|
||||
"between"
|
||||
"align-content:space-between"
|
||||
"around"
|
||||
"align-content:space-around"
|
||||
"evenly"
|
||||
"align-content:space-evenly"
|
||||
"stretch"
|
||||
"align-content:stretch"
|
||||
:else nil)
|
||||
(and (= head "order") (= (len rest) 1))
|
||||
(let
|
||||
((val (first rest)))
|
||||
(cond
|
||||
(= val "first")
|
||||
"order:-9999"
|
||||
(= val "last")
|
||||
"order:9999"
|
||||
(= val "none")
|
||||
"order:0"
|
||||
:else (let
|
||||
((n (parse-int val nil)))
|
||||
(if (nil? n) nil (str "order:" n)))))
|
||||
(and (= head "grid") (empty? rest))
|
||||
"display:grid"
|
||||
(and (= head "grid") (>= (len rest) 2) (= (first rest) "cols"))
|
||||
(let
|
||||
((val (join "-" (slice rest 1))))
|
||||
(cond
|
||||
(= val "none")
|
||||
"grid-template-columns:none"
|
||||
(= val "subgrid")
|
||||
"grid-template-columns:subgrid"
|
||||
:else (let
|
||||
((n (parse-int val nil)))
|
||||
(if
|
||||
(nil? n)
|
||||
nil
|
||||
(str "grid-template-columns:repeat(" n ",minmax(0,1fr))")))))
|
||||
(and (= head "grid") (>= (len rest) 2) (= (first rest) "rows"))
|
||||
(let
|
||||
((val (join "-" (slice rest 1))))
|
||||
(cond
|
||||
(= val "none")
|
||||
"grid-template-rows:none"
|
||||
(= val "subgrid")
|
||||
"grid-template-rows:subgrid"
|
||||
:else (let
|
||||
((n (parse-int val nil)))
|
||||
(if
|
||||
(nil? n)
|
||||
nil
|
||||
(str "grid-template-rows:repeat(" n ",minmax(0,1fr))")))))
|
||||
(and (= head "grid") (>= (len rest) 2) (= (first rest) "flow"))
|
||||
(case
|
||||
(nth rest 1)
|
||||
"row"
|
||||
"grid-auto-flow:row"
|
||||
"col"
|
||||
"grid-auto-flow:column"
|
||||
"dense"
|
||||
"grid-auto-flow:dense"
|
||||
:else nil)
|
||||
(and (= head "col") (>= (len rest) 2))
|
||||
(let
|
||||
((sub (first rest)) (val (join "-" (slice rest 1))))
|
||||
(cond
|
||||
(and (= sub "span") (= val "full"))
|
||||
"grid-column:1 / -1"
|
||||
(= sub "span")
|
||||
(let
|
||||
((n (parse-int val nil)))
|
||||
(if (nil? n) nil (str "grid-column:span " n " / span " n)))
|
||||
(= sub "start")
|
||||
(str "grid-column-start:" val)
|
||||
(= sub "end")
|
||||
(str "grid-column-end:" val)
|
||||
:else nil))
|
||||
(and (= head "row") (>= (len rest) 2))
|
||||
(let
|
||||
((sub (first rest)) (val (join "-" (slice rest 1))))
|
||||
(cond
|
||||
(and (= sub "span") (= val "full"))
|
||||
"grid-row:1 / -1"
|
||||
(= sub "span")
|
||||
(let
|
||||
((n (parse-int val nil)))
|
||||
(if (nil? n) nil (str "grid-row:span " n " / span " n)))
|
||||
(= sub "start")
|
||||
(str "grid-row-start:" val)
|
||||
(= sub "end")
|
||||
(str "grid-row-end:" val)
|
||||
:else nil))
|
||||
(and (= head "auto") (>= (len rest) 2))
|
||||
(let
|
||||
((sub (first rest)) (val (join "-" (slice rest 1))))
|
||||
(cond
|
||||
(and (= sub "cols") (= val "auto"))
|
||||
"grid-auto-columns:auto"
|
||||
(and (= sub "cols") (= val "min"))
|
||||
"grid-auto-columns:min-content"
|
||||
(and (= sub "cols") (= val "max"))
|
||||
"grid-auto-columns:max-content"
|
||||
(and (= sub "cols") (= val "fr"))
|
||||
"grid-auto-columns:minmax(0,1fr)"
|
||||
(and (= sub "rows") (= val "auto"))
|
||||
"grid-auto-rows:auto"
|
||||
(and (= sub "rows") (= val "min"))
|
||||
"grid-auto-rows:min-content"
|
||||
(and (= sub "rows") (= val "max"))
|
||||
"grid-auto-rows:max-content"
|
||||
(and (= sub "rows") (= val "fr"))
|
||||
"grid-auto-rows:minmax(0,1fr)"
|
||||
:else nil))
|
||||
(and
|
||||
(= (len parts) 1)
|
||||
(or
|
||||
(= head "relative")
|
||||
(= head "absolute")
|
||||
(= head "fixed")
|
||||
(= head "sticky")
|
||||
(= head "static")))
|
||||
(str "position:" head)
|
||||
(and
|
||||
(or
|
||||
(= head "top")
|
||||
(= head "right")
|
||||
(= head "bottom")
|
||||
(= head "left"))
|
||||
(= (len rest) 1))
|
||||
(let
|
||||
((v (tw-spacing-value (first rest))))
|
||||
(if (nil? v) nil (str head ":" v)))
|
||||
(and (= head "inset") (= (len rest) 1))
|
||||
(let
|
||||
((v (tw-spacing-value (first rest))))
|
||||
(if (nil? v) nil (str "inset:" v)))
|
||||
(and (= head "inset") (= (len rest) 2))
|
||||
(let
|
||||
((dir (first rest)) (v (tw-spacing-value (nth rest 1))))
|
||||
(if
|
||||
(nil? v)
|
||||
nil
|
||||
(case
|
||||
dir
|
||||
"x"
|
||||
(str "left:" v ";right:" v)
|
||||
"y"
|
||||
(str "top:" v ";bottom:" v)
|
||||
:else nil)))
|
||||
(and (= head "z") (= (len rest) 1))
|
||||
(if
|
||||
(= (first rest) "auto")
|
||||
"z-index:auto"
|
||||
(let
|
||||
((n (parse-int (first rest) nil)))
|
||||
(if (nil? n) nil (str "z-index:" n))))
|
||||
(and (or (= head "w") (= head "h")) (= (len rest) 1))
|
||||
(let
|
||||
((prop (if (= head "w") "width" "height")) (val (first rest)))
|
||||
(cond
|
||||
(= val "full")
|
||||
(str prop ":100%")
|
||||
(= val "screen")
|
||||
(str prop (if (= head "w") ":100vw" ":100vh"))
|
||||
(= val "auto")
|
||||
(str prop ":auto")
|
||||
(= val "min")
|
||||
(str prop ":min-content")
|
||||
(= val "max")
|
||||
(str prop ":max-content")
|
||||
(= val "fit")
|
||||
(str prop ":fit-content")
|
||||
(contains? val "/")
|
||||
(let
|
||||
((frac (split val "/")))
|
||||
(if
|
||||
(= (len frac) 2)
|
||||
(let
|
||||
((num (parse-int (first frac) nil))
|
||||
(den (parse-int (nth frac 1) nil)))
|
||||
(if
|
||||
(or (nil? num) (nil? den))
|
||||
nil
|
||||
(str prop ":" (* (/ num den) 100) "%")))
|
||||
nil))
|
||||
:else (let
|
||||
((n (parse-int val nil)))
|
||||
(if (nil? n) nil (str prop ":" (* n 0.25) "rem")))))
|
||||
(and (= head "max") (>= (len rest) 2) (= (first rest) "w"))
|
||||
(let
|
||||
((val-name (join "-" (slice rest 1)))
|
||||
(val (get tw-max-widths val-name)))
|
||||
(if (nil? val) nil (str "max-width:" val)))
|
||||
(and (= head "max") (>= (len rest) 2) (= (first rest) "h"))
|
||||
(let
|
||||
((val (first (slice rest 1))))
|
||||
(cond
|
||||
(= val "full")
|
||||
"max-height:100%"
|
||||
(= val "screen")
|
||||
"max-height:100vh"
|
||||
(= val "none")
|
||||
"max-height:none"
|
||||
:else (let
|
||||
((n (parse-int val nil)))
|
||||
(if (nil? n) nil (str "max-height:" (* n 0.25) "rem")))))
|
||||
(and (= head "min") (>= (len rest) 2) (= (first rest) "w"))
|
||||
(let
|
||||
((val-name (join "-" (slice rest 1)))
|
||||
(val (get tw-min-widths val-name)))
|
||||
(if (nil? val) nil (str "min-width:" val)))
|
||||
(and (= head "min") (>= (len rest) 2) (= (first rest) "h"))
|
||||
(let
|
||||
((val (first (slice rest 1))))
|
||||
(cond
|
||||
(= val "0")
|
||||
"min-height:0px"
|
||||
(= val "full")
|
||||
"min-height:100%"
|
||||
(= val "screen")
|
||||
"min-height:100vh"
|
||||
:else nil))
|
||||
(and (= head "overflow") (= (len rest) 1))
|
||||
(str "overflow:" (first rest))
|
||||
(and (= head "overflow") (= (len rest) 2))
|
||||
(str "overflow-" (first rest) ":" (nth rest 1))
|
||||
(and (= head "aspect") (= (len rest) 1))
|
||||
(case
|
||||
(first rest)
|
||||
"auto"
|
||||
"aspect-ratio:auto"
|
||||
"square"
|
||||
"aspect-ratio:1 / 1"
|
||||
"video"
|
||||
"aspect-ratio:16 / 9"
|
||||
:else nil)
|
||||
(and (= head "object") (= (len rest) 1))
|
||||
(str "object-fit:" (first rest))
|
||||
(and (= (len parts) 1) (= head "visible"))
|
||||
"visibility:visible"
|
||||
(and (= (len parts) 1) (= head "invisible"))
|
||||
"visibility:hidden"
|
||||
(and (= (len parts) 1) (= head "collapse"))
|
||||
"visibility:collapse"
|
||||
(and (= (len parts) 1) (= head "container"))
|
||||
"width:100%;max-width:100%"
|
||||
(and (= (len parts) 1) (= head "isolate"))
|
||||
"isolation:isolate"
|
||||
:else nil))))
|
||||
213
shared/sx/templates/tw-type.sx
Normal file
213
shared/sx/templates/tw-type.sx
Normal file
@@ -0,0 +1,213 @@
|
||||
(define tw-sizes {:xs "font-size:0.75rem;line-height:1rem" :3xl "font-size:1.875rem;line-height:2.25rem" :7xl "font-size:4.5rem;line-height:1" :sm "font-size:0.875rem;line-height:1.25rem" :8xl "font-size:6rem;line-height:1" :xl "font-size:1.25rem;line-height:1.75rem" :6xl "font-size:3.75rem;line-height:1" :9xl "font-size:8rem;line-height:1" :5xl "font-size:3rem;line-height:1" :lg "font-size:1.125rem;line-height:1.75rem" :2xl "font-size:1.5rem;line-height:2rem" :base "font-size:1rem;line-height:1.5rem" :4xl "font-size:2.25rem;line-height:2.5rem"})
|
||||
|
||||
(define tw-weights {:light "300" :semibold "600" :bold "700" :extrabold "800" :black "900" :extralight "200" :thin "100" :medium "500" :normal "400"})
|
||||
|
||||
(define tw-families {:mono "ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace" :sans "ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,\"Noto Sans\",sans-serif" :serif "ui-serif,Georgia,Cambria,\"Times New Roman\",Times,serif"})
|
||||
|
||||
(define tw-alignments {:center true :end true :left true :right true :start true :justify true})
|
||||
|
||||
(define tw-leading {:tight "1.25" :9 "2.25rem" :loose "2" :relaxed "1.625" :3 "0.75rem" :8 "2rem" :5 "1.25rem" :4 "1rem" :6 "1.5rem" :snug "1.375" :none "1" :normal "1.5" :7 "1.75rem" :10 "2.5rem"})
|
||||
|
||||
(define tw-tracking {:wide "0.025em" :tight "-0.025em" :tighter "-0.05em" :wider "0.05em" :widest "0.1em" :normal "0em"})
|
||||
|
||||
(define
|
||||
tw-resolve-type
|
||||
(fn
|
||||
(token)
|
||||
(let
|
||||
((parts (split token "-"))
|
||||
(head (first parts))
|
||||
(rest (slice parts 1)))
|
||||
(cond
|
||||
(and
|
||||
(= head "text")
|
||||
(= (len rest) 1)
|
||||
(not (nil? (get tw-sizes (first rest)))))
|
||||
(get tw-sizes (first rest))
|
||||
(and
|
||||
(= head "text")
|
||||
(= (len rest) 1)
|
||||
(get tw-alignments (first rest)))
|
||||
(str "text-align:" (first rest))
|
||||
(and
|
||||
(= (len parts) 1)
|
||||
(or
|
||||
(= head "uppercase")
|
||||
(= head "lowercase")
|
||||
(= head "capitalize")))
|
||||
(str "text-transform:" head)
|
||||
(and (= (len parts) 2) (= head "normal") (= (first rest) "case"))
|
||||
"text-transform:none"
|
||||
(and
|
||||
(= head "font")
|
||||
(= (len rest) 1)
|
||||
(not (nil? (get tw-weights (first rest)))))
|
||||
(str "font-weight:" (get tw-weights (first rest)))
|
||||
(and
|
||||
(= head "font")
|
||||
(= (len rest) 1)
|
||||
(not (nil? (get tw-families (first rest)))))
|
||||
(str "font-family:" (get tw-families (first rest)))
|
||||
(and (= (len parts) 1) (= head "italic"))
|
||||
"font-style:italic"
|
||||
(and (= (len parts) 2) (= head "not") (= (first rest) "italic"))
|
||||
"font-style:normal"
|
||||
(and (= head "leading") (= (len rest) 1))
|
||||
(let
|
||||
((val (get tw-leading (first rest))))
|
||||
(if (nil? val) nil (str "line-height:" val)))
|
||||
(and (= head "tracking") (= (len rest) 1))
|
||||
(let
|
||||
((val (get tw-tracking (first rest))))
|
||||
(if (nil? val) nil (str "letter-spacing:" val)))
|
||||
(and (= head "whitespace") (= (len rest) 1))
|
||||
(case
|
||||
(first rest)
|
||||
"normal"
|
||||
"white-space:normal"
|
||||
"nowrap"
|
||||
"white-space:nowrap"
|
||||
"pre"
|
||||
"white-space:pre"
|
||||
"pre-line"
|
||||
"white-space:pre-line"
|
||||
"pre-wrap"
|
||||
"white-space:pre-wrap"
|
||||
"break-spaces"
|
||||
"white-space:break-spaces"
|
||||
:else nil)
|
||||
(and (= head "whitespace") (= (len rest) 2))
|
||||
(let
|
||||
((val (join "-" rest)))
|
||||
(case
|
||||
val
|
||||
"pre-line"
|
||||
"white-space:pre-line"
|
||||
"pre-wrap"
|
||||
"white-space:pre-wrap"
|
||||
"break-spaces"
|
||||
"white-space:break-spaces"
|
||||
:else nil))
|
||||
(and (= head "break") (= (len rest) 1))
|
||||
(case
|
||||
(first rest)
|
||||
"normal"
|
||||
"overflow-wrap:normal;word-break:normal"
|
||||
"words"
|
||||
"overflow-wrap:break-word"
|
||||
"all"
|
||||
"word-break:break-all"
|
||||
"keep"
|
||||
"word-break:keep-all"
|
||||
:else nil)
|
||||
(and (= (len parts) 1) (= head "truncate"))
|
||||
"overflow:hidden;text-overflow:ellipsis;white-space:nowrap"
|
||||
(and (= head "line") (= (len rest) 2) (= (first rest) "clamp"))
|
||||
(let
|
||||
((val (nth rest 1)))
|
||||
(if
|
||||
(= val "none")
|
||||
"overflow:visible;display:block;-webkit-line-clamp:unset"
|
||||
(let
|
||||
((n (parse-int val nil)))
|
||||
(if
|
||||
(nil? n)
|
||||
nil
|
||||
(str
|
||||
"overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:"
|
||||
n)))))
|
||||
(and (= head "indent") (= (len rest) 1))
|
||||
(let
|
||||
((v (tw-spacing-value (first rest))))
|
||||
(if (nil? v) nil (str "text-indent:" v)))
|
||||
(and (= head "align") (= (len rest) 1))
|
||||
(case
|
||||
(first rest)
|
||||
"baseline"
|
||||
"vertical-align:baseline"
|
||||
"top"
|
||||
"vertical-align:top"
|
||||
"middle"
|
||||
"vertical-align:middle"
|
||||
"bottom"
|
||||
"vertical-align:bottom"
|
||||
"text-top"
|
||||
"vertical-align:text-top"
|
||||
"text-bottom"
|
||||
"vertical-align:text-bottom"
|
||||
"sub"
|
||||
"vertical-align:sub"
|
||||
"super"
|
||||
"vertical-align:super"
|
||||
:else nil)
|
||||
(and (= head "align") (= (len rest) 2))
|
||||
(let
|
||||
((val (join "-" rest)))
|
||||
(case
|
||||
val
|
||||
"text-top"
|
||||
"vertical-align:text-top"
|
||||
"text-bottom"
|
||||
"vertical-align:text-bottom"
|
||||
:else nil))
|
||||
(and (= head "list") (= (len rest) 1))
|
||||
(case
|
||||
(first rest)
|
||||
"none"
|
||||
"list-style-type:none"
|
||||
"disc"
|
||||
"list-style-type:disc"
|
||||
"decimal"
|
||||
"list-style-type:decimal"
|
||||
"inside"
|
||||
"list-style-position:inside"
|
||||
"outside"
|
||||
"list-style-position:outside"
|
||||
:else nil)
|
||||
(and
|
||||
(= head "text")
|
||||
(= (len rest) 1)
|
||||
(or
|
||||
(= (first rest) "wrap")
|
||||
(= (first rest) "nowrap")
|
||||
(= (first rest) "balance")
|
||||
(= (first rest) "pretty")))
|
||||
(str "text-wrap:" (first rest))
|
||||
(and (= head "hyphens") (= (len rest) 1))
|
||||
(str "hyphens:" (first rest))
|
||||
(and (= head "content") (= (len rest) 1) (= (first rest) "none"))
|
||||
"content:none"
|
||||
(and (= (len parts) 1) (= head "antialiased"))
|
||||
"-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale"
|
||||
(and
|
||||
(= (len parts) 2)
|
||||
(= head "subpixel")
|
||||
(= (first rest) "antialiased"))
|
||||
"-webkit-font-smoothing:auto;-moz-osx-font-smoothing:auto"
|
||||
(and (= (len parts) 2) (= (first rest) "nums"))
|
||||
(case
|
||||
head
|
||||
"tabular"
|
||||
"font-variant-numeric:tabular-nums"
|
||||
"proportional"
|
||||
"font-variant-numeric:proportional-nums"
|
||||
"lining"
|
||||
"font-variant-numeric:lining-nums"
|
||||
"oldstyle"
|
||||
"font-variant-numeric:oldstyle-nums"
|
||||
:else nil)
|
||||
(and (= (len parts) 2) (= (first rest) "fractions"))
|
||||
(case
|
||||
head
|
||||
"diagonal"
|
||||
"font-variant-numeric:diagonal-fractions"
|
||||
"stacked"
|
||||
"font-variant-numeric:stacked-fractions"
|
||||
:else nil)
|
||||
(and (= (len parts) 2) (= head "normal") (= (first rest) "nums"))
|
||||
"font-variant-numeric:normal"
|
||||
(and (= (len parts) 1) (= head "ordinal"))
|
||||
"font-variant-numeric:ordinal"
|
||||
(and (= (len parts) 2) (= head "slashed") (= (first rest) "zero"))
|
||||
"font-variant-numeric:slashed-zero"
|
||||
:else nil))))
|
||||
444
shared/sx/templates/tw.sx
Normal file
444
shared/sx/templates/tw.sx
Normal file
@@ -0,0 +1,444 @@
|
||||
(define colour-bases {:orange {:s 95 :h 25} :cyan {:s 94 :h 188} :sky {:s 89 :h 199} :pink {:s 81 :h 330} :zinc {:s 5 :h 240} :amber {:s 92 :h 38} :neutral {:s 0 :h 0} :lime {:s 78 :h 84} :violet {:s 70 :h 263} :fuchsia {:s 84 :h 292} :stone {:s 6 :h 25} :black {:s 0 :h 0} :teal {:s 80 :h 173} :gray {:s 9 :h 220} :red {:s 72 :h 0} :rose {:s 89 :h 350} :blue {:s 91 :h 217} :emerald {:s 84 :h 160} :green {:s 71 :h 142} :yellow {:s 96 :h 48} :purple {:s 81 :h 271} :indigo {:s 84 :h 239} :white {:s 0 :h 0} :slate {:s 16 :h 215}})
|
||||
|
||||
(define lerp (fn (a b t) (+ a (* t (- b a)))))
|
||||
|
||||
(define
|
||||
shade-to-lightness
|
||||
(fn
|
||||
(shade)
|
||||
(cond
|
||||
(<= shade 50)
|
||||
(lerp 100 97 (/ shade 50))
|
||||
(<= shade 100)
|
||||
(lerp 97 93 (/ (- shade 50) 50))
|
||||
(<= shade 200)
|
||||
(lerp 93 87 (/ (- shade 100) 100))
|
||||
(<= shade 300)
|
||||
(lerp 87 77 (/ (- shade 200) 100))
|
||||
(<= shade 400)
|
||||
(lerp 77 64 (/ (- shade 300) 100))
|
||||
(<= shade 500)
|
||||
(lerp 64 53 (/ (- shade 400) 100))
|
||||
(<= shade 600)
|
||||
(lerp 53 45 (/ (- shade 500) 100))
|
||||
(<= shade 700)
|
||||
(lerp 45 38 (/ (- shade 600) 100))
|
||||
(<= shade 800)
|
||||
(lerp 38 30 (/ (- shade 700) 100))
|
||||
(<= shade 900)
|
||||
(lerp 30 21 (/ (- shade 800) 100))
|
||||
(<= shade 950)
|
||||
(lerp 21 13 (/ (- shade 900) 50))
|
||||
:else 13)))
|
||||
|
||||
(define
|
||||
colour
|
||||
(fn
|
||||
(name shade)
|
||||
(let
|
||||
((base (get colour-bases name)))
|
||||
(if
|
||||
(nil? base)
|
||||
name
|
||||
(if
|
||||
(= name "white")
|
||||
"#ffffff"
|
||||
(if
|
||||
(= name "black")
|
||||
"#000000"
|
||||
(let
|
||||
((h (get base "h"))
|
||||
(s (get base "s"))
|
||||
(l (shade-to-lightness shade)))
|
||||
(str "hsl(" h "," s "%," (round l) "%)"))))))))
|
||||
|
||||
(define tw-colour-props {:ring "--tw-ring-color" :outline "outline-color" :bg "background-color" :accent "accent-color" :border "border-color" :stroke "stroke" :text "color" :fill "fill"})
|
||||
|
||||
(define tw-breakpoints {:sm "640px" :xl "1280px" :md "768px" :lg "1024px" :2xl "1536px"})
|
||||
|
||||
(define tw-states {:focus ":focus" :before "::before" :first ":first-child" :disabled ":disabled" :required ":required" :even ":nth-child(even)" :hover ":hover" :focus-visible ":focus-visible" :last ":last-child" :visited ":visited" :odd ":nth-child(odd)" :active ":active" :focus-within ":focus-within" :checked ":checked" :placeholder "::placeholder" :after "::after"})
|
||||
|
||||
(define
|
||||
tw-spacing-value
|
||||
(fn
|
||||
(v)
|
||||
(cond
|
||||
(= v "auto")
|
||||
"auto"
|
||||
(= v "px")
|
||||
"1px"
|
||||
(= v "0")
|
||||
"0px"
|
||||
(= v "0.5")
|
||||
"0.125rem"
|
||||
(= v "1.5")
|
||||
"0.375rem"
|
||||
(= v "2.5")
|
||||
"0.625rem"
|
||||
(= v "3.5")
|
||||
"0.875rem"
|
||||
:else (let
|
||||
((n (parse-int v nil)))
|
||||
(if (nil? n) nil (str (* n 0.25) "rem"))))))
|
||||
|
||||
(define
|
||||
tw-template
|
||||
(fn
|
||||
(tmpl v)
|
||||
(let
|
||||
((i (index-of tmpl "{v}")))
|
||||
(if
|
||||
(< i 0)
|
||||
tmpl
|
||||
(let
|
||||
((result (str (substring tmpl 0 i) v (substring tmpl (+ i 3) (len tmpl)))))
|
||||
(let
|
||||
((j (index-of result "{v}")))
|
||||
(if
|
||||
(< j 0)
|
||||
result
|
||||
(str
|
||||
(substring result 0 j)
|
||||
v
|
||||
(substring result (+ j 3) (len result))))))))))
|
||||
|
||||
(define tw-shadow-sizes {:sm "0 1px 2px 0 rgb(0 0 0 / 0.05)" :xl "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)" :md "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)" :inner "inset 0 2px 4px 0 rgb(0 0 0 / 0.05)" :lg "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)" :2xl "0 25px 50px -12px rgb(0 0 0 / 0.25)" :none "0 0 #0000" : "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)"})
|
||||
|
||||
(define tw-rounded-sizes {:3xl "1.5rem" :sm "0.125rem" :xl "0.75rem" :full "9999px" :md "0.375rem" :lg "0.5rem" :2xl "1rem" :none "0" : "0.25rem"})
|
||||
|
||||
(define tw-border-widths {:0 "0px" :2 "2px" :8 "8px" :4 "4px" : "1px"})
|
||||
|
||||
(define
|
||||
tw-resolve-style
|
||||
(fn
|
||||
(token)
|
||||
(let
|
||||
((parts (split token "-"))
|
||||
(head (first parts))
|
||||
(rest (slice parts 1)))
|
||||
(cond
|
||||
(and
|
||||
(get tw-colour-props head)
|
||||
(>= (len rest) 2)
|
||||
(not (nil? (parse-int (last rest) nil)))
|
||||
(not
|
||||
(nil?
|
||||
(get colour-bases (join "-" (slice rest 0 (- (len rest) 1)))))))
|
||||
(let
|
||||
((css-prop (get tw-colour-props head))
|
||||
(cname (join "-" (slice rest 0 (- (len rest) 1))))
|
||||
(shade (parse-int (last rest) 0)))
|
||||
(str css-prop ":" (colour cname shade)))
|
||||
(and
|
||||
(get tw-colour-props head)
|
||||
(= (len rest) 1)
|
||||
(or
|
||||
(= (first rest) "white")
|
||||
(= (first rest) "black")
|
||||
(= (first rest) "transparent")
|
||||
(= (first rest) "current")
|
||||
(= (first rest) "inherit")))
|
||||
(let
|
||||
((css-prop (get tw-colour-props head))
|
||||
(val
|
||||
(case
|
||||
(first rest)
|
||||
"white"
|
||||
"#ffffff"
|
||||
"black"
|
||||
"#000000"
|
||||
"transparent"
|
||||
"transparent"
|
||||
"current"
|
||||
"currentColor"
|
||||
"inherit"
|
||||
"inherit")))
|
||||
(str css-prop ":" val))
|
||||
(= head "rounded")
|
||||
(cond
|
||||
(empty? rest)
|
||||
(str "border-radius:" (get tw-rounded-sizes ""))
|
||||
(and
|
||||
(= (len rest) 1)
|
||||
(not (nil? (get tw-rounded-sizes (first rest)))))
|
||||
(str "border-radius:" (get tw-rounded-sizes (first rest)))
|
||||
(and
|
||||
(>= (len rest) 1)
|
||||
(or
|
||||
(= (first rest) "t")
|
||||
(= (first rest) "b")
|
||||
(= (first rest) "l")
|
||||
(= (first rest) "r")))
|
||||
(let
|
||||
((size (if (>= (len rest) 2) (get tw-rounded-sizes (nth rest 1)) (get tw-rounded-sizes "")))
|
||||
(dir (first rest)))
|
||||
(if
|
||||
(nil? size)
|
||||
nil
|
||||
(case
|
||||
dir
|
||||
"t"
|
||||
(str
|
||||
"border-top-left-radius:"
|
||||
size
|
||||
";border-top-right-radius:"
|
||||
size)
|
||||
"b"
|
||||
(str
|
||||
"border-bottom-left-radius:"
|
||||
size
|
||||
";border-bottom-right-radius:"
|
||||
size)
|
||||
"l"
|
||||
(str
|
||||
"border-top-left-radius:"
|
||||
size
|
||||
";border-bottom-left-radius:"
|
||||
size)
|
||||
"r"
|
||||
(str
|
||||
"border-top-right-radius:"
|
||||
size
|
||||
";border-bottom-right-radius:"
|
||||
size)
|
||||
:else nil)))
|
||||
:else nil)
|
||||
(= head "border")
|
||||
(cond
|
||||
(empty? rest)
|
||||
"border-width:1px"
|
||||
(and
|
||||
(= (len rest) 1)
|
||||
(not (nil? (get tw-border-widths (first rest)))))
|
||||
(str "border-width:" (get tw-border-widths (first rest)))
|
||||
(and
|
||||
(= (len rest) 1)
|
||||
(or
|
||||
(= (first rest) "t")
|
||||
(= (first rest) "b")
|
||||
(= (first rest) "l")
|
||||
(= (first rest) "r")
|
||||
(= (first rest) "x")
|
||||
(= (first rest) "y")))
|
||||
(let
|
||||
((side (first rest)))
|
||||
(case
|
||||
side
|
||||
"t"
|
||||
"border-top-width:1px"
|
||||
"b"
|
||||
"border-bottom-width:1px"
|
||||
"l"
|
||||
"border-left-width:1px"
|
||||
"r"
|
||||
"border-right-width:1px"
|
||||
"x"
|
||||
"border-left-width:1px;border-right-width:1px"
|
||||
"y"
|
||||
"border-top-width:1px;border-bottom-width:1px"
|
||||
:else nil))
|
||||
(and
|
||||
(= (len rest) 2)
|
||||
(not (nil? (get tw-border-widths (nth rest 1)))))
|
||||
(let
|
||||
((side (first rest)) (w (get tw-border-widths (nth rest 1))))
|
||||
(case
|
||||
side
|
||||
"t"
|
||||
(str "border-top-width:" w)
|
||||
"b"
|
||||
(str "border-bottom-width:" w)
|
||||
"l"
|
||||
(str "border-left-width:" w)
|
||||
"r"
|
||||
(str "border-right-width:" w)
|
||||
:else nil))
|
||||
:else nil)
|
||||
(= head "shadow")
|
||||
(let
|
||||
((size-key (if (empty? rest) "" (join "-" rest))))
|
||||
(let
|
||||
((val (get tw-shadow-sizes size-key)))
|
||||
(if (nil? val) nil (str "box-shadow:" val))))
|
||||
(and (= head "opacity") (= (len rest) 1))
|
||||
(let
|
||||
((n (parse-int (first rest) nil)))
|
||||
(if (nil? n) nil (str "opacity:" (/ n 100))))
|
||||
(and (= head "ring") (or (empty? rest) (= (len rest) 1)))
|
||||
(let
|
||||
((w (if (empty? rest) "3px" (let ((n (parse-int (first rest) nil))) (if (nil? n) nil (str n "px"))))))
|
||||
(if
|
||||
(nil? w)
|
||||
nil
|
||||
(str
|
||||
"box-shadow:0 0 0 "
|
||||
w
|
||||
" var(--tw-ring-color, rgb(59 130 246 / 0.5))")))
|
||||
(= head "outline")
|
||||
(cond
|
||||
(and (= (len rest) 1) (= (first rest) "none"))
|
||||
"outline:2px solid transparent;outline-offset:2px"
|
||||
(empty? rest)
|
||||
"outline-style:solid"
|
||||
:else nil)
|
||||
(= head "transition")
|
||||
(cond
|
||||
(empty? rest)
|
||||
"transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms"
|
||||
(= (first rest) "colors")
|
||||
"transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms"
|
||||
(= (first rest) "all")
|
||||
"transition-property:all;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms"
|
||||
(= (first rest) "none")
|
||||
"transition-property:none"
|
||||
(= (first rest) "opacity")
|
||||
"transition-property:opacity;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms"
|
||||
(= (first rest) "shadow")
|
||||
"transition-property:box-shadow;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms"
|
||||
(= (first rest) "transform")
|
||||
"transition-property:transform;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms"
|
||||
:else nil)
|
||||
(and (= head "duration") (= (len rest) 1))
|
||||
(str "transition-duration:" (first rest) "ms")
|
||||
(= head "ease")
|
||||
(let
|
||||
((val (join "-" rest)))
|
||||
(case
|
||||
val
|
||||
"linear"
|
||||
"transition-timing-function:linear"
|
||||
"in"
|
||||
"transition-timing-function:cubic-bezier(0.4,0,1,1)"
|
||||
"out"
|
||||
"transition-timing-function:cubic-bezier(0,0,0.2,1)"
|
||||
"in-out"
|
||||
"transition-timing-function:cubic-bezier(0.4,0,0.2,1)"
|
||||
:else nil))
|
||||
(and (= head "cursor") (= (len rest) 1))
|
||||
(str "cursor:" (first rest))
|
||||
(and (= head "cursor") (= (len rest) 2))
|
||||
(str "cursor:" (join "-" rest))
|
||||
(and
|
||||
(= head "pointer")
|
||||
(= (len rest) 2)
|
||||
(= (first rest) "events"))
|
||||
(str "pointer-events:" (nth rest 1))
|
||||
(and (= head "select") (= (len rest) 1))
|
||||
(str "user-select:" (first rest))
|
||||
(and (= head "appearance") (= (len rest) 1))
|
||||
(str "appearance:" (first rest))
|
||||
(and
|
||||
(= (len parts) 1)
|
||||
(or
|
||||
(= head "underline")
|
||||
(= head "overline")
|
||||
(= head "line-through")))
|
||||
(str "text-decoration-line:" head)
|
||||
(and (= (len parts) 2) (= head "no") (= (first rest) "underline"))
|
||||
"text-decoration-line:none"
|
||||
(and (= head "scale") (= (len rest) 1))
|
||||
(let
|
||||
((n (parse-int (first rest) nil)))
|
||||
(if (nil? n) nil (str "transform:scale(" (/ n 100) ")")))
|
||||
(and (= head "rotate") (= (len rest) 1))
|
||||
(str "transform:rotate(" (first rest) "deg)")
|
||||
(= head "animate")
|
||||
(cond
|
||||
(= (first rest) "spin")
|
||||
"animation:spin 1s linear infinite"
|
||||
(= (first rest) "ping")
|
||||
"animation:ping 1s cubic-bezier(0,0,0.2,1) infinite"
|
||||
(= (first rest) "pulse")
|
||||
"animation:pulse 2s cubic-bezier(0.4,0,0.6,1) infinite"
|
||||
(= (first rest) "bounce")
|
||||
"animation:bounce 1s infinite"
|
||||
(= (first rest) "none")
|
||||
"animation:none"
|
||||
:else nil)
|
||||
:else nil))))
|
||||
|
||||
(define
|
||||
tw-process-token
|
||||
(fn
|
||||
(token)
|
||||
(let
|
||||
((colon-parts (split token ":")) (n (len colon-parts)))
|
||||
(let
|
||||
((bp nil) (state nil) (base nil))
|
||||
(cond
|
||||
(= n 1)
|
||||
(do (set! base (first colon-parts)))
|
||||
(= n 2)
|
||||
(do
|
||||
(if
|
||||
(get tw-breakpoints (first colon-parts))
|
||||
(do
|
||||
(set! bp (first colon-parts))
|
||||
(set! base (nth colon-parts 1)))
|
||||
(do
|
||||
(set! state (first colon-parts))
|
||||
(set! base (nth colon-parts 1)))))
|
||||
(= n 3)
|
||||
(do
|
||||
(set! bp (first colon-parts))
|
||||
(set! state (nth colon-parts 1))
|
||||
(set! base (nth colon-parts 2)))
|
||||
:else (do (set! base token)))
|
||||
(let
|
||||
((negative (and (>= (len base) 2) (= (substring base 0 1) "-")))
|
||||
(actual-base (if negative (substring base 1 (len base)) base)))
|
||||
(let
|
||||
((css (or (tw-resolve-style actual-base) (tw-resolve-layout actual-base) (tw-resolve-type actual-base))))
|
||||
(if
|
||||
(nil? css)
|
||||
nil
|
||||
(let
|
||||
((final-css (if negative (str css) css))
|
||||
(cls
|
||||
(str
|
||||
"sx-"
|
||||
(replace token ":" "-")
|
||||
(replace token "." "d")))
|
||||
(selector
|
||||
(str "." cls (if state (get tw-states state) "")))
|
||||
(rule
|
||||
(if
|
||||
bp
|
||||
(str
|
||||
"@media(min-width:"
|
||||
(get tw-breakpoints bp)
|
||||
"){"
|
||||
selector
|
||||
"{"
|
||||
final-css
|
||||
"}}")
|
||||
(str selector "{" final-css "}"))))
|
||||
{:rule rule :cls cls}))))))))
|
||||
|
||||
(defcomp
|
||||
~tw
|
||||
(&key tokens)
|
||||
(let
|
||||
((token-list (filter (fn (t) (not (= t ""))) (split (or tokens "") " ")))
|
||||
(results (map tw-process-token token-list))
|
||||
(valid (filter (fn (r) (not (nil? r))) results))
|
||||
(classes (map (fn (r) (get r "cls")) valid))
|
||||
(rules (map (fn (r) (get r "rule")) valid))
|
||||
(_ (for-each (fn (rule) (collect! "cssx" rule)) rules)))
|
||||
(if (empty? classes) nil (make-spread {:class (join " " classes) :data-tw (or tokens "")}))))
|
||||
|
||||
(defcomp
|
||||
~tw/flush
|
||||
()
|
||||
:affinity :client
|
||||
(let
|
||||
((rules (collected "cssx")) (head-style (dom-query "#sx-css")))
|
||||
(when
|
||||
head-style
|
||||
(clear-collected! "cssx")
|
||||
(when
|
||||
(not (empty? rules))
|
||||
(dom-set-prop
|
||||
head-style
|
||||
"textContent"
|
||||
(str (dom-get-prop head-style "textContent") (join "" rules)))))))
|
||||
Reference in New Issue
Block a user