Skip to content

Commit 94834ac

Browse files
committed
Prototype new Text components
1 parent 52d76a4 commit 94834ac

File tree

5 files changed

+387
-4
lines changed

5 files changed

+387
-4
lines changed

docs/App.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ const componentv2Loaders = [
126126
"Clip",
127127
"Link",
128128
"QRCode",
129-
"Slat"
129+
"Slat",
130+
"Text"
130131
].map(fromComponentPathv2);
131132

132133
const App = () => {

docs/Examples2/Text.example.purs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
module Lumi.Components2.Examples.Text where
2+
3+
import Prelude
4+
5+
import Lumi.Components (($$$))
6+
import Lumi.Components.Example (example)
7+
import Lumi.Components2.Box (box)
8+
import Lumi.Components2.Text as T
9+
import Lumi.Styles (StyleModifier)
10+
import Lumi.Styles as S
11+
import React.Basic (JSX, fragment)
12+
13+
docs :: JSX
14+
docs =
15+
box
16+
$$$ [ example
17+
$ fragment
18+
$ [ T.sectionHeader $ T.strong $$$ "A tiny story"
19+
, T.subsectionHeader $$$ "Hello!"
20+
, T.paragraph
21+
$$$ [ T.text
22+
$ T.emphasized
23+
$$$ "How are you? "
24+
, T.text
25+
$$$ "Hope you're doing "
26+
, T.text
27+
$ T.strong
28+
$$$ "fine. "
29+
, T.text
30+
$ T.subtext
31+
$ T.muted
32+
$$$ "Yes, I do."
33+
]
34+
, T.paragraph
35+
$$$ [ T.text
36+
$$$ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam congue ligula et odio rutrum, eu imperdiet ante laoreet. Cras mollis faucibus urna, eu luctus ligula condimentum ut. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vivamus et mi mattis, maximus urna id, luctus neque. Sed non lorem molestie nibh suscipit condimentum id quis enim. Nunc tortor elit, posuere eu metus sed, finibus sagittis est. Fusce dapibus lacus vitae augue vulputate, in convallis lectus congue. "
37+
, T.text
38+
$ T.strong
39+
$$$ "Hi! "
40+
, T.text
41+
$ T.subtext
42+
$$$ "Hey!"
43+
]
44+
, T.paragraph
45+
$ T.emphasized
46+
$$$ [ T.text
47+
$$$ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam congue ligula et odio rutrum, eu imperdiet ante laoreet. Cras mollis faucibus urna, eu luctus ligula condimentum ut. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vivamus et mi mattis, maximus urna id, luctus neque. Sed non lorem molestie nibh suscipit condimentum id quis enim. Nunc tortor elit, posuere eu metus sed, finibus sagittis est. Fusce dapibus lacus vitae augue vulputate, in convallis lectus congue. "
48+
, T.text
49+
$ nonItalic
50+
$$$ "Hi! "
51+
, T.text
52+
$ T.subtext
53+
$ nonItalic
54+
$$$ "Hey!"
55+
]
56+
, T.paragraph_
57+
$$$ "Vestibulum eu arcu eget lectus interdum maximus feugiat sed velit. Integer ullamcorper urna quis cursus mattis. Nam vel hendrerit purus. Aliquam fringilla dictum nunc at ornare. Morbi ornare blandit tincidunt. Etiam sodales fringilla libero, vitae pulvinar sapien luctus ac. Proin condimentum vitae risus id vestibulum. Sed sed turpis leo. Quisque ligula leo, facilisis eget metus ullamcorper, aliquet mollis tortor. Donec purus metus, maximus rutrum nunc eget, rhoncus tempor erat. Sed efficitur tellus id velit ullamcorper, ut dignissim neque pretium. Sed id metus porta, efficitur est non, vulputate sapien."
58+
, T.paragraph_
59+
$ T.subtext
60+
$$$ "Donec maximus commodo ipsum vel elementum. Sed at nunc dapibus, vulputate tellus eu, finibus ante. Nunc dolor ante, auctor et rutrum quis, sagittis ut nulla. Integer vel tempus ipsum, vel laoreet orci. Curabitur eu sem bibendum, rhoncus risus vel, tristique mauris. Cras lobortis elit sit amet quam semper pretium. Nullam fermentum ut velit ac cursus."
61+
]
62+
]
63+
64+
nonItalic :: StyleModifier
65+
nonItalic =
66+
S.style_ (S.css { fontStyle: S.str "normal" })

src/Lumi/Components.purs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module Lumi.Components
22
( LumiProps
33
, PropsModifier
4+
, PropsModifier'
45
, propsModifier
56
, LumiComponent
67
, lumiComponent
@@ -35,9 +36,11 @@ type LumiComponent props = (LumiProps props -> LumiProps props) -> JSX
3536
type LumiProps props
3637
= { css :: LumiTheme -> Emotion.Style, className :: String | props }
3738

38-
type PropsModifier props
39+
type PropsModifier props = PropsModifier' props props
40+
41+
type PropsModifier' props props'
3942
= (LumiProps props -> LumiProps props) ->
40-
(LumiProps props -> LumiProps props)
43+
(LumiProps props' -> LumiProps props')
4144

4245
-- | Lift a `props -> props` function for composition with other `PropsModifier` functions.
4346
propsModifier :: forall props. (LumiProps props -> LumiProps props) -> PropsModifier props

src/Lumi/Components2/Text.purs

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
module Lumi.Components2.Text
2+
( Text
3+
, TextProps, TextType
4+
, text
5+
6+
, TextModifier
7+
, body, strong, emphasized, subtext
8+
, muted, color
9+
10+
, ParagraphProps
11+
, paragraph_, paragraph
12+
13+
, Header
14+
, HeaderProps
15+
, subsectionHeader
16+
, sectionHeader
17+
, title
18+
, mainHeader
19+
) where
20+
21+
import Prelude
22+
23+
import Data.Maybe (Maybe(..), fromMaybe, maybe)
24+
import Effect.Unsafe (unsafePerformEffect)
25+
import Lumi.Components (LumiComponent, PropsModifier, PropsModifier', lumiComponent, propsModifier)
26+
import Lumi.Components.Color (ColorMap)
27+
import Lumi.Styles (Style, StyleModifier, StyleProperty)
28+
import Lumi.Styles as S
29+
import Lumi.Styles.Theme (LumiTheme(..), TextMap, textFontSize, textLineHeight, textMargin, useTheme)
30+
import React.Basic (JSX, ReactComponent)
31+
import React.Basic.DOM as R
32+
import React.Basic.Emotion as E
33+
import React.Basic.Hooks as Hooks
34+
35+
-- Text
36+
37+
data Text = Text
38+
39+
data TextType
40+
= Body
41+
| Strong
42+
| Emphasis
43+
| Subtext
44+
45+
type TextProps =
46+
( component :: Text
47+
, type :: Maybe TextType
48+
, content :: String
49+
)
50+
51+
type TextElement = ReactComponent { children :: Array JSX, className :: String }
52+
53+
-- | Inline piece of text whose `content` prop is a `String`. Elements of this
54+
-- | component should preferentially be used only where an inline element is
55+
-- | expected, that is, from within the contents of a block-level element.
56+
-- |
57+
-- | For block-level text elements, please use `paragraph`, `paragraph_`, or the
58+
-- | header components defined in this module.
59+
text :: LumiComponent TextProps
60+
text =
61+
unsafePerformEffect $ lumiComponent "Text" defaults \props -> Hooks.do
62+
theme <- useTheme
63+
pure $
64+
E.element (maybe R.span' textElement props.type)
65+
{ children: [ R.text props.content ]
66+
, className: props.className
67+
, css: defaultTextStyle theme props.type <> props.css theme
68+
}
69+
where
70+
defaults :: Record TextProps
71+
defaults =
72+
{ component: Text
73+
, type: Nothing
74+
, content: ""
75+
}
76+
77+
defaultTextStyle :: LumiTheme -> Maybe TextType -> Style
78+
defaultTextStyle theme ty =
79+
S.css
80+
{ fontSize: maybe S.inherit (px <<< textFontSize theme <<< textTheme) ty
81+
, lineHeight: maybe S.inherit (px <<< textLineHeight theme <<< textTheme) ty
82+
, whiteSpace: S.str "pre-wrap"
83+
, margin: S.str "0"
84+
, padding: S.str "0"
85+
}
86+
87+
textElement :: TextType -> TextElement
88+
textElement Body = R.span'
89+
textElement Strong = R.strong'
90+
textElement Emphasis = R.em'
91+
textElement Subtext = R.small'
92+
93+
textTheme :: TextType -> (forall a. TextMap a -> a)
94+
textTheme =
95+
case _ of
96+
Body -> _.body
97+
Strong -> _.body
98+
Emphasis -> _.body
99+
Subtext -> _.subtext
100+
101+
-- | The `c` type parameter lets us constrain the type of component to which
102+
-- | a text modifier may be applied: `Text`, `Header` or any.
103+
type TextModifier c = forall r. PropsModifier (component :: c, type :: Maybe TextType | r)
104+
105+
body :: TextModifier Text
106+
body = propsModifier _{ type = Just Body }
107+
108+
strong :: forall c. TextModifier c
109+
strong =
110+
propsModifier _{ type = Just Strong }
111+
<<< S.style_ (S.css { fontWeight: S.str "600" })
112+
113+
emphasized :: forall c. TextModifier c
114+
emphasized =
115+
propsModifier _{ type = Just Emphasis }
116+
<<< S.style_ (S.css { fontStyle: S.str "italic" })
117+
118+
subtext :: TextModifier Text
119+
subtext = propsModifier _{ type = Just Subtext }
120+
121+
muted :: forall c. TextModifier c
122+
muted =
123+
S.style \(LumiTheme { colors }) ->
124+
S.css { color: S.color colors.black1 }
125+
126+
color :: forall c. (forall a. ColorMap a -> a) -> TextModifier c
127+
color f =
128+
S.style \(LumiTheme { colors }) ->
129+
S.css { color: S.color (f colors) }
130+
131+
-- Paragraph
132+
133+
type ParagraphProps =
134+
( component :: Text
135+
, type :: Maybe TextType
136+
, content :: Array JSX
137+
)
138+
139+
-- | A variant of `paragraph` whose `content` prop is a `String`.
140+
paragraph_ :: LumiComponent TextProps
141+
paragraph_ = paragraph <<< renderInnerText
142+
where
143+
renderInnerText :: PropsModifier' TextProps ParagraphProps
144+
renderInnerText f props =
145+
let
146+
props' = f $ props { content = "" }
147+
in
148+
props'
149+
{ content =
150+
[ text _
151+
{ component = props'.component
152+
, type = props'.type
153+
, content = props'.content
154+
, css = \_ -> S.css { fontSize: S.inherit, lineHeight: S.inherit }
155+
}
156+
]
157+
}
158+
159+
-- | A `paragraph` is a block-level element that contains text and/or other
160+
-- | inline-level elements, such as images. Paragraphs also define a bottom
161+
-- | margin so that multiple paragraphs in a column can read nicely with proper
162+
-- | spacing.
163+
-- |
164+
-- | Paragraphs are used for laying out text as a concrete, individual and
165+
-- | independent element in a page.
166+
paragraph :: LumiComponent ParagraphProps
167+
paragraph =
168+
unsafePerformEffect $ lumiComponent "Paragraph" defaults \props -> Hooks.do
169+
theme <- useTheme
170+
pure $
171+
E.element R.p'
172+
{ children: props.content
173+
, className: props.className
174+
, css: defaultParagraphStyle theme props.type <> props.css theme
175+
}
176+
where
177+
defaults :: Record ParagraphProps
178+
defaults =
179+
{ component: Text
180+
, type: Nothing
181+
, content: []
182+
}
183+
184+
defaultParagraphStyle :: LumiTheme -> Maybe TextType -> Style
185+
defaultParagraphStyle theme ty =
186+
S.merge
187+
[ S.css
188+
{ whiteSpace: S.str "pre-wrap"
189+
, margin: S.str "0"
190+
, padding: S.str "0"
191+
}
192+
, S.toCSS (textStyle (textTheme (fromMaybe Body ty))) theme
193+
]
194+
195+
-- Headers
196+
197+
data Header = Header
198+
199+
-- Even though the header components never end up using the `type` property, we
200+
-- need it here so that text modifiers such as `strong` may also be applied to
201+
-- headers.
202+
type HeaderProps =
203+
( component :: Header
204+
, type :: Maybe TextType
205+
, content :: String
206+
)
207+
208+
subsectionHeader :: LumiComponent HeaderProps
209+
subsectionHeader = mkHeaderComponent R.h4' <<< textStyle _.subsectionHeader
210+
211+
sectionHeader :: LumiComponent HeaderProps
212+
sectionHeader = mkHeaderComponent R.h3' <<< textStyle _.sectionHeader
213+
214+
title :: LumiComponent HeaderProps
215+
title = mkHeaderComponent R.h2' <<< textStyle _.title
216+
217+
mainHeader :: LumiComponent HeaderProps
218+
mainHeader = mkHeaderComponent R.h1' <<< textStyle _.mainHeader
219+
220+
-- | Create a header component given an HTML wrapper element to be used. The
221+
-- | default header style is that of a "subsection header".
222+
-- |
223+
-- | Headers are block-level elements used for marking/separating sections of a
224+
-- | layout or for displaying important pieces of information in a context.
225+
mkHeaderComponent
226+
:: TextElement
227+
-> LumiComponent HeaderProps
228+
mkHeaderComponent el =
229+
unsafePerformEffect $ lumiComponent "Header" defaults \props -> Hooks.do
230+
theme <- useTheme
231+
pure $
232+
E.element el
233+
{ children: [ R.text props.content ]
234+
, className: props.className
235+
, css: defaultHeaderStyle theme <> props.css theme
236+
}
237+
where
238+
defaults :: Record HeaderProps
239+
defaults =
240+
{ component: Header
241+
, type: Nothing
242+
, content: ""
243+
}
244+
245+
defaultHeaderStyle :: LumiTheme -> Style
246+
defaultHeaderStyle theme =
247+
S.merge
248+
[ S.css
249+
{ fontWeight: S.str "400"
250+
, padding: S.str "0"
251+
, margin: S.str "0"
252+
}
253+
, S.toCSS (textStyle _.subsectionHeader) theme
254+
]
255+
256+
--
257+
258+
-- | Defines the font size, line height and bottom margin values for paragraph
259+
-- | and header elements.
260+
textStyle :: (forall a. TextMap a -> a) -> StyleModifier
261+
textStyle selector =
262+
S.style \theme ->
263+
S.css
264+
{ fontSize: px $ textFontSize theme selector
265+
, lineHeight: px $ textLineHeight theme selector
266+
, marginBottom: px $ textMargin theme selector
267+
}
268+
269+
px :: Int -> StyleProperty
270+
px i = S.str $ show i <> "px"

0 commit comments

Comments
 (0)