diff --git a/src/index.tsx b/src/index.tsx
index 884c5f3..132e1dd 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -216,4 +216,5 @@ export function define(nameOrDef: string | TagDef, defIfNamed?: TagDef) {
}
}
+// shortcut so you only have to import one thing, if you want
define.Styles = Styles
\ No newline at end of file
diff --git a/src/tests/index.test.tsx b/src/tests/index.test.tsx
index 9a130c6..486c098 100644
--- a/src/tests/index.test.tsx
+++ b/src/tests/index.test.tsx
@@ -744,6 +744,147 @@ describe('variants on parts via props', () => {
})
})
+describe('base with attributes', () => {
+ test('extracts element name from base with attributes', () => {
+ const Component = define('InputRadio', {
+ base: 'input[type=radio]',
+ display: 'block'
+ })
+
+ const html = renderToString(Component({}))
+ expect(html).toContain(' {
+ const Component = define('InputCheckbox', {
+ base: 'input[type=checkbox]'
+ })
+
+ const html = renderToString(Component({}))
+ expect(html).toContain('type="checkbox"')
+ })
+
+ test('works without attributes', () => {
+ const Component = define('PlainInput', {
+ base: 'input',
+ padding: 10
+ })
+
+ const html = renderToString(Component({}))
+ expect(html).toContain(' {
+ const Component = define('OverridableInput', {
+ base: 'input[type=text]'
+ })
+
+ const html = renderToString(Component({ type: 'email' }))
+ expect(html).toContain('type="email"')
+ })
+})
+
+describe('selectors with @ and &', () => {
+ test('generates CSS for selectors with @PartName', () => {
+ define('SelectorTest', {
+ parts: {
+ Input: { base: 'input[type=checkbox]', display: 'none' },
+ Label: {
+ base: 'label',
+ color: '#666',
+ selectors: {
+ '@Input:checked + &': {
+ color: 'blue'
+ }
+ }
+ }
+ }
+ })
+
+ const css = getStylesCSS()
+ expect(css).toContain('.SelectorTest_Input:checked + .SelectorTest_Label')
+ expect(css).toContain('color: blue')
+ })
+
+ test('selectors support general sibling combinator ~', () => {
+ define('SiblingTest', {
+ parts: {
+ Trigger: { base: 'input[type=checkbox]' },
+ Content: {
+ display: 'none',
+ selectors: {
+ '@Trigger:checked ~ &': {
+ display: 'block'
+ }
+ }
+ }
+ }
+ })
+
+ const css = getStylesCSS()
+ expect(css).toContain('.SiblingTest_Trigger:checked ~ .SiblingTest_Content')
+ expect(css).toContain('display: block')
+ })
+
+ test('selectors can include pseudo-classes on &', () => {
+ define('PseudoTest', {
+ parts: {
+ Radio: { base: 'input[type=radio]', display: 'none' },
+ Label: {
+ base: 'label',
+ selectors: {
+ '@Radio:checked + &:hover': {
+ background: 'lightblue'
+ }
+ }
+ }
+ }
+ })
+
+ const css = getStylesCSS()
+ expect(css).toContain('.PseudoTest_Radio:checked + .PseudoTest_Label:hover')
+ expect(css).toContain('background: lightblue')
+ })
+
+ test('selectors work on root level', () => {
+ define('RootSelectorTest', {
+ color: 'black',
+ selectors: {
+ '&:hover': {
+ color: 'blue'
+ }
+ }
+ })
+
+ const css = getStylesCSS()
+ expect(css).toContain('.RootSelectorTest:hover')
+ expect(css).toContain('color: blue')
+ })
+
+ test('supports multiple selectors', () => {
+ define('MultiSelectorTest', {
+ parts: {
+ Input: { base: 'input[type=checkbox]' },
+ Label: {
+ selectors: {
+ '@Input:checked + &': { color: 'green' },
+ '@Input:disabled + &': { opacity: 0.5 }
+ }
+ }
+ }
+ })
+
+ const css = getStylesCSS()
+ expect(css).toContain('.MultiSelectorTest_Input:checked + .MultiSelectorTest_Label')
+ expect(css).toContain('color: green')
+ expect(css).toContain('.MultiSelectorTest_Input:disabled + .MultiSelectorTest_Label')
+ expect(css).toContain('opacity: 0.5')
+ })
+})
+
describe('edge cases', () => {
test('handles empty definition', () => {
const Component = define({})
@@ -777,13 +918,12 @@ describe('edge cases', () => {
expect(html).toContain('size-small')
})
- test('does not duplicate styles when registered multiple times', () => {
- define('NoDuplicate', { width: 100 })
- define('NoDuplicate', { width: 200 })
+ test('throws error when defining duplicate component names', () => {
+ define('NoDuplicateTest', { width: 100 })
- const styles = parseCSS(getStylesCSS())
- // Should keep first value (??= operator)
- expect(styles['NoDuplicate']?.['width']).toBe('100px')
+ expect(() => {
+ define('NoDuplicateTest', { width: 200 })
+ }).toThrow('NoDuplicateTest is already defined! Must use unique names.')
})
test('handles complex nested structures', () => {