Skip to content

Commit 575c89b

Browse files
committed
Merge branch 'codebender828-master'
2 parents 32b8b88 + 385e377 commit 575c89b

File tree

5 files changed

+143
-38
lines changed

5 files changed

+143
-38
lines changed

README.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ Remember to register `ThemeProvider` locally.
171171

172172
Add your `ThemeProvider` component:
173173

174-
```JSX
174+
```vue
175175
<theme-provider :theme="{
176176
primary: 'palevioletred'
177177
}">
@@ -230,6 +230,26 @@ const TomatoButton = StyledButton.extend`
230230
export default TomatoButton;
231231
```
232232

233+
### Polymorphic `as` prop
234+
If you want to keep all the styling you've applied to a component but just switch out what's being ultimately rendered (be it a different HTML tag or a different custom component), you can use the "as" prop to do this at runtime. Another powerful feature of the `as` prop is that it preserves styles if the lowest-wrapped component is a `StyledComponent`.
235+
236+
**Example**
237+
In `Component.js`
238+
```js
239+
// Renders a div element by default.
240+
const Component = styled('div', {})``
241+
```
242+
Using the `as` prop in another template/component would be as shown below.
243+
```vue
244+
<template>
245+
<!-- Component will render a button instead of a div -->
246+
<Component as="button" color="red">
247+
Button
248+
</Component>
249+
</template>
250+
```
251+
This sort of thing is very useful in use cases like a navigation bar where some of the items should be links and some just buttons, but all be styled the same way.
252+
233253
### withComponent
234254
Let's say you have a `button` and an `a` tag. You want them to share the exact same style. This is achievable with `.withComponent`.
235255
```JSX

example/index.html

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,16 @@ <h1>Basic Example</h1>
7575
background: ${props => props.theme.tertiary};
7676
`;
7777

78-
const W3 = styled.default(W2)`
79-
&:hover {
80-
background: ${props => props.theme.primary};
81-
}
78+
// Custom Wrapper
79+
const W3 = styled.default(Wrapper, {
80+
color: String,
81+
bg: String
82+
})`
83+
color: ${props => props.color};
84+
background-color: ${props => props.bg};
8285
`
8386

87+
8488
const StyledInput = styled.default.input`
8589
display: block;
8690
width: 100%;
@@ -106,19 +110,39 @@ <h1>Basic Example</h1>
106110
margin: 1.5em 0;
107111
background-color: ${props => props.theme.primary};
108112
opacity: ${props => props.visible ? 1: 0};
113+
114+
&:focus {
115+
outline: none;
116+
border: solid 3px ${props => props.theme.primary};
117+
}
109118
`
110119

111120
const SuperBtn = styled.default(Btn, { visible: Boolean, big: Boolean })`
112121
font-size: ${props => props.big ? 0.8 : 1}em;
113122
`
114123

124+
const CustomBtn = styled.default(Btn, {
125+
color: String,
126+
bg: String
127+
})`
128+
color: ${props => props.color};
129+
background-color: ${props => props.bg};
130+
131+
&:focus {
132+
outline: none;
133+
border: solid 3px ${props => props.color};
134+
}
135+
`
136+
115137
new Vue({
116138
el: '#container',
117139
data: {
118140
text1: 'Hello World, this is my first styled component!',
119141
text2: 'Hello World, this is my first styled component!',
120142
animated1: false,
121-
animated2: false
143+
animated2: false,
144+
element: 'div',
145+
setElement: 'div'
122146
},
123147
template: `
124148
<theme-provider :theme="{
@@ -136,18 +160,44 @@ <h1>Basic Example</h1>
136160
<styled-input v-model="text2" />
137161
<super-btn visible @click="animated2 = !animated2"> An extension of Styled Button </super-btn>
138162
</w-2>
163+
<w-3
164+
bg="#c6f7e6"
165+
>
166+
<custom-title :visible="true">Change Rendered Elements with "as" prop.</custom-title>
167+
<h3>Enter any HTML element and see the element rendered by the styled component change 👇🏽</h3>
168+
<styled-input @keydown.enter="updateElement" placeholder="Enter an HTML element! Like 'button' or 'section'" v-model="element" />
169+
<custom-btn
170+
visible
171+
:as="setElement"
172+
role="button"
173+
tabindex="0"
174+
color="#42a786"
175+
bg="papayawhip"
176+
aria-label="Btn rendered as div"
177+
@keydown.space="updateElement"
178+
>
179+
I am the "btn" component rendered as a "{{ setElement }}" using the \`as\` prop! 🎉
180+
</custom-btn>
181+
</w-3>
139182
</theme-provider>`,
140183
components: {
141184
CustomTitle,
142185
Wrapper,
143186
W2,
187+
W3,
144188
H2,
145189
Btn,
146190
SuperBtn,
191+
CustomBtn,
147192
SuperCustomTitle,
148193
StyledInput,
149194
'theme-provider': styled.ThemeProvider,
150195
},
196+
methods: {
197+
updateElement() {
198+
this.setElement = this.element
199+
}
200+
}
151201
});
152202
</script>
153203
</body>

package-lock.json

Lines changed: 12 additions & 31 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/models/StyledComponent.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import css from '../constructors/css'
22
import normalizeProps from '../utils/normalizeProps'
3+
import isVueComponent from '../utils/isVueComponent'
34

45
export default (ComponentStyle) => {
56
const createStyledComponent = (target, rules, props) => {
@@ -18,6 +19,7 @@ export default (ComponentStyle) => {
1819
}
1920
},
2021
props: {
22+
as: [String, Object],
2123
value: null,
2224
...currentProps,
2325
...prevProps
@@ -38,7 +40,8 @@ export default (ComponentStyle) => {
3840
}
3941

4042
return createElement(
41-
target,
43+
// Check if target is StyledComponent to preserve inner component styles for composition
44+
isVueComponent(target) ? target : this.$props.as || target,
4245
{
4346
class: [this.generatedClassName],
4447
props: this.$props,

src/test/as.test.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import Vue from 'vue'
2+
import expect from 'expect'
3+
import { resetStyled, expectCSSMatches } from './utils'
4+
5+
let styled
6+
7+
describe('"as" polymorphic prop', () => {
8+
beforeEach(() => {
9+
styled = resetStyled()
10+
})
11+
12+
it('should render "as" polymorphic prop element', () => {
13+
const Base = styled.div`
14+
color: blue;
15+
`
16+
const b = new Vue({
17+
render: (h) => h(Base, {
18+
props: {
19+
as: 'button'
20+
}
21+
})
22+
}).$mount()
23+
expect(b.$el.tagName.toLowerCase()).toEqual('button')
24+
})
25+
26+
27+
it('should append base class to new components composing lower level styled components', () => {
28+
const Base = styled.div`
29+
color: blue;
30+
`
31+
const Composed = styled(Base, {
32+
bg: String,
33+
})`
34+
background: ${props => props.bg};
35+
`
36+
37+
const b = new Vue(Base).$mount()
38+
const c = new Vue({
39+
render: (h) => h(Composed, {
40+
props: {
41+
bg: 'yellow',
42+
as: 'dialog'
43+
}
44+
})
45+
}).$mount()
46+
47+
expect(c.$el.tagName.toLowerCase()).toEqual('dialog')
48+
expect(c.$el._prevClass.includes(b.$el._prevClass)).toBeTruthy()
49+
expectCSSMatches('.a{color: blue;} .b{background:yellow;}')
50+
})
51+
})

0 commit comments

Comments
 (0)