Skip to content
This repository was archived by the owner on Feb 18, 2025. It is now read-only.

Commit 6c21bb9

Browse files
committed
Pass options to component style and modify css selector
1 parent 43daf3b commit 6c21bb9

File tree

5 files changed

+205
-140
lines changed

5 files changed

+205
-140
lines changed

src/models/ComponentStyle.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import hashStr from '../vendor/glamor/hash'
44
import type { RuleSet, NameGenerator, Flattener, Stringifier } from '../types'
55
import StyleSheet from './StyleSheet'
66
import isStyledComponent from '../utils/isStyledComponent'
7+
import getComponentCssSelector from '../utils/getComponentCssSelector'
78

89
const isStaticRules = (rules: RuleSet): boolean => {
910
for (let i = 0; i < rules.length; i += 1) {
@@ -33,7 +34,6 @@ export default (nameGenerator: NameGenerator, flatten: Flattener, stringifyRules
3334
isStatic: boolean
3435
lastClassName: ?string
3536

36-
3737
constructor(rules: RuleSet, componentId: string) {
3838
this.rules = rules
3939
this.isStatic = isStaticRules(rules)
@@ -49,7 +49,11 @@ export default (nameGenerator: NameGenerator, flatten: Flattener, stringifyRules
4949
* Hashes it, wraps the whole chunk in a .hash1234 {}
5050
* Returns the hash to be injected on render()
5151
* */
52-
generateAndInjectStyles(executionContext: Object, styleSheet: StyleSheet) {
52+
generateAndInjectStyles(
53+
executionContext: Object,
54+
styleSheet: StyleSheet,
55+
options: Object = {},
56+
) {
5357
const { isStatic, lastClassName } = this
5458
if (isStatic && lastClassName !== undefined) {
5559
return lastClassName
@@ -74,7 +78,9 @@ export default (nameGenerator: NameGenerator, flatten: Flattener, stringifyRules
7478
return name
7579
}
7680

77-
const css = `\n${stringifyRules(flatCSS, `.${name}`)}`
81+
const selector = getComponentCssSelector(name, options)
82+
83+
const css = `\n${stringifyRules(flatCSS, selector)}`
7884
// NOTE: this can only be set when we inject the class-name.
7985
// For some reason, presumably due to how css is stringifyRules behaves in
8086
// differently between client and server, styles break.

src/models/StyleSheet.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export default class StyleSheet {
3131
deferredInjections: { [string]: string } = {}
3232
componentTags: { [string]: Tag }
3333
// helper for `ComponentStyle` to know when it cache static styles.
34-
// staticly styled-component can not safely cache styles on the server
34+
// statically styled-component can not safely cache styles on the server
3535
// without all `ComponentStyle` instances saving a reference to the
3636
// the styleSheet instance they last rendered with,
3737
// or listening to creation / reset events. otherwise you might create

src/models/StyledComponent.js

Lines changed: 148 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -43,162 +43,174 @@ export default (ComponentStyle: Function, constructWithOptions: Function) => {
4343
: componentId
4444
}
4545

46-
class BaseStyledComponent extends Component {
47-
static target: Target
48-
static styledComponentId: string
49-
static attrs: Object
50-
static componentStyle: Object
51-
static warnTooManyClasses: Function
52-
53-
attrs = {}
54-
state = {
55-
theme: null,
56-
generatedClassName: '',
57-
}
58-
unsubscribeId: number = -1
59-
60-
unsubscribeFromContext() {
61-
if (this.unsubscribeId !== -1) {
62-
this.context[CHANNEL_NEXT].unsubscribe(this.unsubscribeId)
46+
const createBaseStyledComponent = (options: Object) => {
47+
class BaseStyledComponent extends Component {
48+
static target: Target
49+
static styledComponentId: string
50+
static attrs: Object
51+
static componentStyle: Object
52+
static warnTooManyClasses: Function
53+
54+
attrs = {}
55+
state = {
56+
theme: null,
57+
generatedClassName: '',
6358
}
64-
}
59+
unsubscribeId: number = -1
6560

66-
buildExecutionContext(theme: any, props: any) {
67-
const { attrs } = this.constructor
68-
const context = { ...props, theme }
69-
if (attrs === undefined) {
70-
return context
61+
unsubscribeFromContext() {
62+
if (this.unsubscribeId !== -1) {
63+
this.context[CHANNEL_NEXT].unsubscribe(this.unsubscribeId)
64+
}
7165
}
7266

73-
this.attrs = Object.keys(attrs).reduce((acc, key) => {
74-
const attr = attrs[key]
75-
// eslint-disable-next-line no-param-reassign
76-
acc[key] = typeof attr === 'function' ? attr(context) : attr
77-
return acc
78-
}, {})
79-
80-
return { ...context, ...this.attrs }
81-
}
82-
83-
generateAndInjectStyles(theme: any, props: any) {
84-
const { attrs, componentStyle, warnTooManyClasses } = this.constructor
85-
const styleSheet = this.context[CONTEXT_KEY] || StyleSheet.instance
86-
87-
// staticaly styled-components don't need to build an execution context object,
88-
// and shouldn't be increasing the number of class names
89-
if (componentStyle.isStatic && attrs === undefined) {
90-
return componentStyle.generateAndInjectStyles(STATIC_EXECUTION_CONTEXT, styleSheet)
91-
} else {
92-
const executionContext = this.buildExecutionContext(theme, props)
93-
const className = componentStyle.generateAndInjectStyles(executionContext, styleSheet)
67+
buildExecutionContext(theme: any, props: any) {
68+
const { attrs } = this.constructor
69+
const context = { ...props, theme }
70+
if (attrs === undefined) {
71+
return context
72+
}
9473

95-
if (warnTooManyClasses !== undefined) warnTooManyClasses(className)
74+
this.attrs = Object.keys(attrs).reduce((acc, key) => {
75+
const attr = attrs[key]
76+
// eslint-disable-next-line no-param-reassign
77+
acc[key] = typeof attr === 'function' ? attr(context) : attr
78+
return acc
79+
}, {})
9680

97-
return className
81+
return { ...context, ...this.attrs }
9882
}
99-
}
10083

101-
componentWillMount() {
102-
const { componentStyle } = this.constructor
103-
const styledContext = this.context[CHANNEL_NEXT]
104-
105-
// If this is a staticaly-styled component, we don't need to the theme
106-
// to generate or build styles.
107-
if (componentStyle.isStatic) {
108-
const generatedClassName = this.generateAndInjectStyles(
109-
STATIC_EXECUTION_CONTEXT,
110-
this.props,
111-
)
112-
this.setState({ generatedClassName })
113-
// If there is a theme in the context, subscribe to the event emitter. This
114-
// is necessary due to pure components blocking context updates, this circumvents
115-
// that by updating when an event is emitted
116-
} else if (styledContext !== undefined) {
117-
const { subscribe } = styledContext
118-
this.unsubscribeId = subscribe(nextTheme => {
119-
// This will be called once immediately
120-
const theme = determineTheme(this.props, nextTheme, this.constructor.defaultProps)
121-
const generatedClassName = this.generateAndInjectStyles(theme, this.props)
122-
123-
this.setState({ theme, generatedClassName })
124-
})
125-
} else {
126-
// eslint-disable-next-line react/prop-types
127-
const theme = this.props.theme || {}
128-
const generatedClassName = this.generateAndInjectStyles(
129-
theme,
130-
this.props,
131-
)
132-
this.setState({ theme, generatedClassName })
84+
generateAndInjectStyles(theme: any, props: any) {
85+
const { attrs, componentStyle, warnTooManyClasses } = this.constructor
86+
const styleSheet = this.context[CONTEXT_KEY] || StyleSheet.instance
87+
88+
// staticaly styled-components don't need to build an execution context object,
89+
// and shouldn't be increasing the number of class names
90+
if (componentStyle.isStatic && attrs === undefined) {
91+
return componentStyle.generateAndInjectStyles(
92+
STATIC_EXECUTION_CONTEXT,
93+
styleSheet,
94+
options,
95+
)
96+
} else {
97+
const executionContext = this.buildExecutionContext(theme, props)
98+
const className = componentStyle.generateAndInjectStyles(
99+
executionContext,
100+
styleSheet,
101+
options,
102+
)
103+
104+
if (warnTooManyClasses !== undefined) warnTooManyClasses(className)
105+
106+
return className
107+
}
133108
}
134-
}
135109

136-
componentWillReceiveProps(nextProps: { theme?: Theme, [key: string]: any }) {
137-
// If this is a staticaly-styled component, we don't need to listen to
138-
// props changes to update styles
139-
const { componentStyle } = this.constructor
140-
if (componentStyle.isStatic) {
141-
return
110+
componentWillMount() {
111+
const { componentStyle } = this.constructor
112+
const styledContext = this.context[CHANNEL_NEXT]
113+
114+
// If this is a staticaly-styled component, we don't need to the theme
115+
// to generate or build styles.
116+
if (componentStyle.isStatic) {
117+
const generatedClassName = this.generateAndInjectStyles(
118+
STATIC_EXECUTION_CONTEXT,
119+
this.props,
120+
)
121+
this.setState({ generatedClassName })
122+
// If there is a theme in the context, subscribe to the event emitter. This
123+
// is necessary due to pure components blocking context updates, this circumvents
124+
// that by updating when an event is emitted
125+
} else if (styledContext !== undefined) {
126+
const { subscribe } = styledContext
127+
this.unsubscribeId = subscribe(nextTheme => {
128+
// This will be called once immediately
129+
const theme = determineTheme(this.props, nextTheme, this.constructor.defaultProps)
130+
const generatedClassName = this.generateAndInjectStyles(theme, this.props)
131+
132+
this.setState({ theme, generatedClassName })
133+
})
134+
} else {
135+
// eslint-disable-next-line react/prop-types
136+
const theme = this.props.theme || {}
137+
const generatedClassName = this.generateAndInjectStyles(
138+
theme,
139+
this.props,
140+
)
141+
this.setState({ theme, generatedClassName })
142+
}
142143
}
143144

144-
this.setState((oldState) => {
145-
const theme = determineTheme(nextProps, oldState.theme, this.constructor.defaultProps)
146-
const generatedClassName = this.generateAndInjectStyles(theme, nextProps)
147-
148-
return { theme, generatedClassName }
149-
})
150-
}
151-
152-
componentWillUnmount() {
153-
this.unsubscribeFromContext()
154-
}
155-
156-
render() {
157-
// eslint-disable-next-line react/prop-types
158-
const { innerRef } = this.props
159-
const { generatedClassName } = this.state
160-
const { styledComponentId, target } = this.constructor
145+
componentWillReceiveProps(nextProps: { theme?: Theme, [key: string]: any }) {
146+
// If this is a staticaly-styled component, we don't need to listen to
147+
// props changes to update styles
148+
const { componentStyle } = this.constructor
149+
if (componentStyle.isStatic) {
150+
return
151+
}
161152

162-
const isTargetTag = isTag(target)
153+
this.setState((oldState) => {
154+
const theme = determineTheme(nextProps, oldState.theme, this.constructor.defaultProps)
155+
const generatedClassName = this.generateAndInjectStyles(theme, nextProps)
163156

164-
const className = [
165-
// eslint-disable-next-line react/prop-types
166-
this.props.className,
167-
styledComponentId,
168-
this.attrs.className,
169-
generatedClassName,
170-
].filter(Boolean).join(' ')
171-
172-
const baseProps = {
173-
...this.attrs,
174-
className,
157+
return { theme, generatedClassName }
158+
})
175159
}
176160

177-
if (isStyledComponent(target)) {
178-
baseProps.innerRef = innerRef
179-
} else {
180-
baseProps.ref = innerRef
161+
componentWillUnmount() {
162+
this.unsubscribeFromContext()
181163
}
182164

183-
const propsForElement = Object
184-
.keys(this.props)
185-
.reduce((acc, propName) => {
186-
// Don't pass through non HTML tags through to HTML elements
187-
// always omit innerRef
188-
if (
189-
propName !== 'innerRef' &&
190-
propName !== 'className' &&
191-
(!isTargetTag || validAttr(propName))
192-
) {
193-
// eslint-disable-next-line no-param-reassign
194-
acc[propName] = this.props[propName]
195-
}
165+
render() {
166+
// eslint-disable-next-line react/prop-types
167+
const { innerRef } = this.props
168+
const { generatedClassName } = this.state
169+
const { styledComponentId, target } = this.constructor
170+
171+
const isTargetTag = isTag(target)
172+
173+
const className = [
174+
// eslint-disable-next-line react/prop-types
175+
this.props.className,
176+
styledComponentId,
177+
this.attrs.className,
178+
generatedClassName,
179+
].filter(Boolean).join(' ')
180+
181+
const baseProps = {
182+
...this.attrs,
183+
className,
184+
}
196185

197-
return acc
198-
}, baseProps)
186+
if (isStyledComponent(target)) {
187+
baseProps.innerRef = innerRef
188+
} else {
189+
baseProps.ref = innerRef
190+
}
199191

200-
return createElement(target, propsForElement)
192+
const propsForElement = Object
193+
.keys(this.props)
194+
.reduce((acc, propName) => {
195+
// Don't pass through non HTML tags through to HTML elements
196+
// always omit innerRef
197+
if (
198+
propName !== 'innerRef' &&
199+
propName !== 'className' &&
200+
(!isTargetTag || validAttr(propName))
201+
) {
202+
// eslint-disable-next-line no-param-reassign
203+
acc[propName] = this.props[propName]
204+
}
205+
206+
return acc
207+
}, baseProps)
208+
209+
return createElement(target, propsForElement)
210+
}
201211
}
212+
213+
return BaseStyledComponent
202214
}
203215

204216
const createStyledComponent = (
@@ -209,7 +221,7 @@ export default (ComponentStyle: Function, constructWithOptions: Function) => {
209221
const {
210222
displayName = isTag(target) ? `styled.${target}` : `Styled(${getComponentName(target)})`,
211223
componentId = generateId(options.displayName, options.parentComponentId),
212-
ParentComponent = BaseStyledComponent,
224+
ParentComponent = createBaseStyledComponent(options),
213225
rules: extendingRules,
214226
attrs,
215227
} = options
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// @flow
2+
3+
/**
4+
* Adjusts the css selector for the component's css to increase specificity when needed
5+
*/
6+
export default function getComponentCssSelector(componentName: string, options: Object) {
7+
if (options && options.namespaceClasses) {
8+
let namespaceClass = options.namespaceClasses
9+
if (Array.isArray(options.namespaceClasses)) {
10+
namespaceClass = options.namespaceClasses.join(' .')
11+
}
12+
13+
return `.${namespaceClass} .${componentName}`
14+
}
15+
16+
return `.${componentName}`
17+
}

0 commit comments

Comments
 (0)