@oztix/roadie-components 0.2.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/Button.d.ts +31 -111
  2. package/dist/Button.js +1 -1
  3. package/dist/Code.d.ts +15 -5
  4. package/dist/Code.js +1 -1
  5. package/dist/Heading.d.ts +23 -6
  6. package/dist/Heading.js +1 -1
  7. package/dist/Text.d.ts +24 -37
  8. package/dist/Text.js +1 -1
  9. package/dist/View.d.ts +7 -70
  10. package/dist/View.js +1 -1
  11. package/dist/_chunks/chunk-6FIUWNC7.js +9 -0
  12. package/dist/_chunks/chunk-6FIUWNC7.js.map +1 -0
  13. package/dist/_chunks/chunk-GSK3G4DW.js +9 -0
  14. package/dist/_chunks/chunk-GSK3G4DW.js.map +1 -0
  15. package/dist/_chunks/chunk-P5L5LN6E.js +9 -0
  16. package/dist/_chunks/chunk-P5L5LN6E.js.map +1 -0
  17. package/dist/_chunks/chunk-RJEJUZ3O.js +7 -0
  18. package/dist/_chunks/chunk-RJEJUZ3O.js.map +1 -0
  19. package/dist/_chunks/chunk-VDMZIGT2.js +10 -0
  20. package/dist/_chunks/chunk-VDMZIGT2.js.map +1 -0
  21. package/dist/index.d.ts +5 -6
  22. package/dist/index.js +5 -5
  23. package/package.json +10 -5
  24. package/src/components/Button/Button.test.tsx +156 -0
  25. package/src/components/Button/index.tsx +48 -0
  26. package/src/components/Code/Code.test.tsx +85 -0
  27. package/src/components/Code/index.tsx +37 -0
  28. package/src/components/Heading/Heading.test.tsx +128 -0
  29. package/src/components/Heading/index.tsx +52 -0
  30. package/src/components/Text/Text.test.tsx +121 -0
  31. package/src/components/Text/index.tsx +54 -0
  32. package/src/components/View/View.test.tsx +161 -0
  33. package/src/components/View/index.tsx +12 -0
  34. package/src/components/index.ts +14 -0
  35. package/src/index.test.tsx +37 -0
  36. package/src/index.tsx +5 -0
  37. package/dist/_chunks/chunk-5EABNA3A.js +0 -8
  38. package/dist/_chunks/chunk-5EABNA3A.js.map +0 -1
  39. package/dist/_chunks/chunk-72747LP3.js +0 -8
  40. package/dist/_chunks/chunk-72747LP3.js.map +0 -1
  41. package/dist/_chunks/chunk-76TTGNAE.js +0 -8
  42. package/dist/_chunks/chunk-76TTGNAE.js.map +0 -1
  43. package/dist/_chunks/chunk-CNVMCX74.js +0 -8
  44. package/dist/_chunks/chunk-CNVMCX74.js.map +0 -1
  45. package/dist/_chunks/chunk-JDVDZGUN.js +0 -9
  46. package/dist/_chunks/chunk-JDVDZGUN.js.map +0 -1
@@ -0,0 +1,156 @@
1
+ import { render } from '@testing-library/react'
2
+ import userEvent from '@testing-library/user-event'
3
+ import { describe, expect, it, vi } from 'vitest'
4
+
5
+ import { Button } from '.'
6
+
7
+ describe('Button', () => {
8
+ it('renders with default props', () => {
9
+ const { getByText } = render(<Button>Click me</Button>)
10
+ const button = getByText('Click me')
11
+ expect(button).toBeInTheDocument()
12
+ expect(button.tagName.toLowerCase()).toBe('button')
13
+ expect(button).toHaveClass(
14
+ 'button',
15
+ 'button--emphasis_default',
16
+ 'button--size_md'
17
+ )
18
+ })
19
+
20
+ it('renders with different emphasis', () => {
21
+ const { rerender, getByText } = render(
22
+ <Button emphasis='strong' colorPalette='accent'>
23
+ Strong
24
+ </Button>
25
+ )
26
+ let button = getByText('Strong')
27
+ expect(button).toHaveClass(
28
+ 'button--emphasis_strong',
29
+ 'color-palette_accent'
30
+ )
31
+
32
+ rerender(<Button emphasis='default'>Default</Button>)
33
+ button = getByText('Default')
34
+ expect(button).toHaveClass('button--emphasis_default')
35
+
36
+ rerender(<Button emphasis='subtle'>Subtle</Button>)
37
+ button = getByText('Subtle')
38
+ expect(button).toHaveClass('button--emphasis_subtle')
39
+
40
+ rerender(<Button emphasis='subtler'>subtler</Button>)
41
+ button = getByText('subtler')
42
+ expect(button).toHaveClass('button--emphasis_subtler')
43
+ })
44
+
45
+ it('renders with different sizes', () => {
46
+ const { rerender, getByText } = render(<Button size='sm'>Small</Button>)
47
+ let button = getByText('Small')
48
+ expect(button).toHaveClass('button--size_sm')
49
+
50
+ rerender(<Button size='md'>Medium</Button>)
51
+ button = getByText('Medium')
52
+ expect(button).toHaveClass('button--size_md')
53
+
54
+ rerender(<Button size='lg'>Large</Button>)
55
+ button = getByText('Large')
56
+ expect(button).toHaveClass('button--size_lg')
57
+ })
58
+
59
+ it('renders with different color palettes', () => {
60
+ const { rerender, getByText } = render(
61
+ <Button emphasis='strong' colorPalette='accent'>
62
+ Accent
63
+ </Button>
64
+ )
65
+ let button = getByText('Accent')
66
+ expect(button).toHaveClass('color-palette_accent')
67
+
68
+ rerender(
69
+ <Button emphasis='strong' colorPalette='success'>
70
+ Success
71
+ </Button>
72
+ )
73
+ button = getByText('Success')
74
+ expect(button).toHaveClass('color-palette_success')
75
+
76
+ rerender(
77
+ <Button emphasis='strong' colorPalette='warning'>
78
+ Warning
79
+ </Button>
80
+ )
81
+ button = getByText('Warning')
82
+ expect(button).toHaveClass('color-palette_warning')
83
+
84
+ rerender(
85
+ <Button emphasis='strong' colorPalette='danger'>
86
+ Danger
87
+ </Button>
88
+ )
89
+ button = getByText('Danger')
90
+ expect(button).toHaveClass('color-palette_danger')
91
+ })
92
+
93
+ it('handles disabled state', () => {
94
+ const { getByText } = render(<Button disabled>Disabled</Button>)
95
+ const button = getByText('Disabled')
96
+ expect(button).toBeDisabled()
97
+ expect(button).toHaveClass('button')
98
+ })
99
+
100
+ it('calls onClick when clicked', async () => {
101
+ const handleClick = vi.fn()
102
+ const user = userEvent.setup()
103
+
104
+ const { getByText } = render(
105
+ <Button onClick={handleClick}>Click me</Button>
106
+ )
107
+ const button = getByText('Click me')
108
+
109
+ await user.click(button)
110
+ expect(handleClick).toHaveBeenCalledTimes(1)
111
+ })
112
+
113
+ it('does not call onClick when disabled', async () => {
114
+ const handleClick = vi.fn()
115
+ const user = userEvent.setup()
116
+
117
+ const { getByText } = render(
118
+ <Button disabled onClick={handleClick}>
119
+ Click me
120
+ </Button>
121
+ )
122
+ const button = getByText('Click me')
123
+
124
+ await user.click(button)
125
+ expect(handleClick).not.toHaveBeenCalled()
126
+ })
127
+
128
+ it('applies custom className', () => {
129
+ const { getByText } = render(
130
+ <Button className='custom-class'>Custom</Button>
131
+ )
132
+ const button = getByText('Custom')
133
+ expect(button).toHaveClass('custom-class')
134
+ })
135
+
136
+ it('combines multiple props', () => {
137
+ const { getByText } = render(
138
+ <Button
139
+ emphasis='strong'
140
+ size='lg'
141
+ colorPalette='accent'
142
+ className='custom-class'
143
+ >
144
+ Combined
145
+ </Button>
146
+ )
147
+ const button = getByText('Combined')
148
+ expect(button).toHaveClass(
149
+ 'button',
150
+ 'button--emphasis_strong',
151
+ 'button--size_lg',
152
+ 'color-palette_accent',
153
+ 'custom-class'
154
+ )
155
+ })
156
+ })
@@ -0,0 +1,48 @@
1
+ import React from 'react'
2
+
3
+ import { ark } from '@ark-ui/react/factory'
4
+
5
+ import type { ColorPalette } from '@oztix/roadie-core'
6
+ import { styled } from '@oztix/roadie-core/jsx'
7
+ import type { HTMLStyledProps } from '@oztix/roadie-core/jsx'
8
+ import { type ButtonVariantProps, button } from '@oztix/roadie-core/recipes'
9
+
10
+ /**
11
+ * A button component with various emphasis levels and sizes
12
+ */
13
+ export interface ButtonProps extends HTMLStyledProps<'button'> {
14
+ /**
15
+ * The visual emphasis of the button
16
+ * @default 'default'
17
+ */
18
+ emphasis?: ButtonVariantProps['emphasis']
19
+
20
+ /**
21
+ * The size of the button
22
+ * @default 'md'
23
+ */
24
+ size?: ButtonVariantProps['size']
25
+
26
+ /**
27
+ * The color palette to use for the button
28
+ * @default 'neutral'
29
+ */
30
+ colorPalette?: ColorPalette
31
+
32
+ /**
33
+ * When true, the component will pass props to its child component
34
+ */
35
+ asChild?: boolean
36
+
37
+ /**
38
+ * The content to display
39
+ */
40
+ children?: React.ReactNode
41
+ }
42
+
43
+ export const Button = styled(
44
+ ark.button,
45
+ button
46
+ ) as React.ForwardRefExoticComponent<ButtonProps>
47
+
48
+ Button.displayName = 'Button'
@@ -0,0 +1,85 @@
1
+ import { render } from '@testing-library/react'
2
+ import { describe, expect, it } from 'vitest'
3
+
4
+ import { Code } from './index'
5
+
6
+ describe('Code', () => {
7
+ it('renders with default props', () => {
8
+ const { getByText } = render(<Code>const x = 42;</Code>)
9
+ const code = getByText('const x = 42;')
10
+ expect(code).toBeInTheDocument()
11
+ expect(code.tagName.toLowerCase()).toBe('code')
12
+ expect(code).toHaveClass('code', 'code--emphasis_default')
13
+ })
14
+
15
+ it('renders with different emphasis', () => {
16
+ const { rerender, getByText } = render(
17
+ <Code emphasis='strong'>strong code</Code>
18
+ )
19
+ let code = getByText('strong code')
20
+ expect(code).toHaveClass('code--emphasis_strong')
21
+
22
+ rerender(<Code emphasis='subtle'>subtle code</Code>)
23
+ code = getByText('subtle code')
24
+ expect(code).toHaveClass('code--emphasis_subtle')
25
+
26
+ rerender(<Code emphasis='subtler'>subtler code</Code>)
27
+ code = getByText('subtler code')
28
+ expect(code).toHaveClass('code--emphasis_subtler')
29
+ })
30
+
31
+ it('renders with different color palettes', () => {
32
+ const { rerender, getByText } = render(
33
+ <Code colorPalette='accent'>accent code</Code>
34
+ )
35
+ let code = getByText('accent code')
36
+ expect(code).toBeInTheDocument()
37
+
38
+ rerender(<Code colorPalette='success'>success code</Code>)
39
+ code = getByText('success code')
40
+ expect(code).toBeInTheDocument()
41
+
42
+ rerender(<Code colorPalette='danger'>danger code</Code>)
43
+ code = getByText('danger code')
44
+ expect(code).toBeInTheDocument()
45
+ })
46
+
47
+ it('applies custom font size', () => {
48
+ const { getByText } = render(<Code fontSize='lg'>Large code</Code>)
49
+ const code = getByText('Large code')
50
+ expect(code).toHaveClass('fs_lg')
51
+ })
52
+
53
+ it('inherits Text props', () => {
54
+ const { getByText } = render(<Code color='accent.fg'>Colored code</Code>)
55
+ const code = getByText('Colored code')
56
+ expect(code).toHaveClass('c_accent.fg')
57
+ })
58
+
59
+ it('applies line clamp', () => {
60
+ const { getByText } = render(<Code lineClamp={2}>Clamped code</Code>)
61
+ const code = getByText('Clamped code')
62
+ expect(code).toHaveClass('lc_2')
63
+ })
64
+
65
+ it('combines multiple props', () => {
66
+ const { getByText } = render(
67
+ <Code
68
+ emphasis='subtler'
69
+ colorPalette='accent'
70
+ fontSize='lg'
71
+ className='custom-class'
72
+ >
73
+ Combined styles
74
+ </Code>
75
+ )
76
+ const code = getByText('Combined styles')
77
+ expect(code).toHaveClass(
78
+ 'code',
79
+ 'code--emphasis_subtler',
80
+ 'color-palette_accent',
81
+ 'fs_lg',
82
+ 'custom-class'
83
+ )
84
+ })
85
+ })
@@ -0,0 +1,37 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ import { ark } from '@ark-ui/react/factory'
4
+
5
+ import type { ColorPalette } from '@oztix/roadie-core'
6
+ import { styled } from '@oztix/roadie-core/jsx'
7
+ import type { HTMLStyledProps } from '@oztix/roadie-core/jsx'
8
+ import { type CodeVariantProps, code } from '@oztix/roadie-core/recipes'
9
+
10
+ /**
11
+ * A code component that inherits from Text and renders as a code element
12
+ */
13
+ export interface CodeProps extends HTMLStyledProps<'code'> {
14
+ /**
15
+ * The appearance of the code block
16
+ * @default 'default'
17
+ */
18
+ emphasis?: CodeVariantProps['emphasis']
19
+
20
+ /**
21
+ * The color palette to use for the code
22
+ * @default 'neutral'
23
+ */
24
+ colorPalette?: ColorPalette
25
+
26
+ /**
27
+ * The content to display
28
+ */
29
+ children?: ReactNode
30
+ }
31
+
32
+ export const Code = styled(
33
+ ark.code,
34
+ code
35
+ ) as React.ForwardRefExoticComponent<CodeProps>
36
+
37
+ Code.displayName = 'Code'
@@ -0,0 +1,128 @@
1
+ import { render } from '@testing-library/react'
2
+ import { describe, expect, it } from 'vitest'
3
+
4
+ import { Heading } from './index'
5
+
6
+ describe('Heading', () => {
7
+ it('renders with default props', () => {
8
+ const { getByText } = render(<Heading>Hello World</Heading>)
9
+ const heading = getByText('Hello World')
10
+ expect(heading).toBeInTheDocument()
11
+ expect(heading.tagName.toLowerCase()).toBe('h2')
12
+ expect(heading).toHaveClass('heading', 'heading--emphasis_default')
13
+ })
14
+
15
+ it('renders with different heading levels', () => {
16
+ const levels: Array<'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'> = [
17
+ 'h1',
18
+ 'h2',
19
+ 'h3',
20
+ 'h4',
21
+ 'h5',
22
+ 'h6'
23
+ ]
24
+
25
+ levels.forEach((level) => {
26
+ const { rerender, getByText } = render(
27
+ <Heading as={level}>{level} Heading</Heading>
28
+ )
29
+ const heading = getByText(`${level} Heading`)
30
+ expect(heading).toBeInTheDocument()
31
+ expect(heading.tagName.toLowerCase()).toBe(level)
32
+ expect(heading).toHaveClass('heading')
33
+ rerender(<></>)
34
+ })
35
+ })
36
+
37
+ it('applies text style', () => {
38
+ const { getByText } = render(
39
+ <Heading textStyle='display.ui.1'>Large Heading</Heading>
40
+ )
41
+ const heading = getByText('Large Heading')
42
+ expect(heading).toHaveClass('textStyle_display.ui.1')
43
+ })
44
+
45
+ it('inherits Text props', () => {
46
+ const { getByTestId } = render(
47
+ <Heading
48
+ color='neutral.fg.subtle'
49
+ data-testid='heading'
50
+ title='tooltip'
51
+ aria-label='Accessible heading'
52
+ >
53
+ Styled Heading
54
+ </Heading>
55
+ )
56
+ const heading = getByTestId('heading')
57
+ expect(heading).toHaveClass('heading', 'c_neutral.fg.subtle')
58
+ expect(heading).toHaveAttribute('title', 'tooltip')
59
+ expect(heading).toHaveAttribute('aria-label', 'Accessible heading')
60
+ })
61
+
62
+ it('applies line clamp', () => {
63
+ const { getByText } = render(
64
+ <Heading lineClamp={2}>Multi-line heading that should be clamped</Heading>
65
+ )
66
+ const heading = getByText('Multi-line heading that should be clamped')
67
+ expect(heading).toHaveClass('lc_2')
68
+ })
69
+
70
+ it('renders with different emphasis', () => {
71
+ const { rerender, getByText } = render(
72
+ <Heading emphasis='default'>Default Heading</Heading>
73
+ )
74
+ let heading = getByText('Default Heading')
75
+ expect(heading).toHaveClass('heading--emphasis_default')
76
+
77
+ rerender(<Heading emphasis='strong'>Strong Heading</Heading>)
78
+ heading = getByText('Strong Heading')
79
+ expect(heading).toHaveClass('heading--emphasis_strong')
80
+
81
+ rerender(<Heading emphasis='subtle'>Subtle Heading</Heading>)
82
+ heading = getByText('Subtle Heading')
83
+ expect(heading).toHaveClass('heading--emphasis_subtle')
84
+
85
+ rerender(<Heading emphasis='subtler'>Subtler Heading</Heading>)
86
+ heading = getByText('Subtler Heading')
87
+ expect(heading).toHaveClass('heading--emphasis_subtler')
88
+ })
89
+
90
+ it('renders with different color palettes', () => {
91
+ const { rerender, getByText } = render(
92
+ <Heading colorPalette='neutral'>Neutral Heading</Heading>
93
+ )
94
+ let heading = getByText('Neutral Heading')
95
+ expect(heading).toBeInTheDocument()
96
+
97
+ rerender(<Heading colorPalette='brand'>Brand Heading</Heading>)
98
+ heading = getByText('Brand Heading')
99
+ expect(heading).toBeInTheDocument()
100
+
101
+ rerender(<Heading colorPalette='success'>Success Heading</Heading>)
102
+ heading = getByText('Success Heading')
103
+ expect(heading).toBeInTheDocument()
104
+ })
105
+
106
+ it('combines multiple props', () => {
107
+ const { getByText } = render(
108
+ <Heading
109
+ as='h1'
110
+ textStyle='display.ui.1'
111
+ emphasis='strong'
112
+ colorPalette='brand'
113
+ className='custom-class'
114
+ >
115
+ Combined styles
116
+ </Heading>
117
+ )
118
+ const heading = getByText('Combined styles')
119
+ expect(heading.tagName.toLowerCase()).toBe('h1')
120
+ expect(heading).toHaveClass(
121
+ 'heading',
122
+ 'heading--emphasis_strong',
123
+ 'textStyle_display.ui.1',
124
+ 'color-palette_brand',
125
+ 'custom-class'
126
+ )
127
+ })
128
+ })
@@ -0,0 +1,52 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ import { ark } from '@ark-ui/react/factory'
4
+
5
+ import type { ColorPalette } from '@oztix/roadie-core'
6
+ import { type HTMLStyledProps, styled } from '@oztix/roadie-core/jsx'
7
+ import { type HeadingVariantProps, heading } from '@oztix/roadie-core/recipes'
8
+ import type { JsxStyleProps } from '@oztix/roadie-core/types'
9
+
10
+ type HeadingElement = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'label'
11
+ type HeadingStyle = Extract<JsxStyleProps['textStyle'], `display.${string}`>
12
+
13
+ /**
14
+ * A heading component that uses display styles for titles and section headers
15
+ */
16
+ export interface HeadingProps extends HTMLStyledProps<'h2'> {
17
+ /**
18
+ * The heading element to render
19
+ * @default 'h2'
20
+ */
21
+ as?: HeadingElement
22
+
23
+ /**
24
+ * The text style to use for the heading
25
+ * @default 'display.ui'
26
+ */
27
+ textStyle?: HeadingStyle
28
+
29
+ /**
30
+ * The color palette to use for the heading
31
+ * @default 'neutral'
32
+ */
33
+ colorPalette?: ColorPalette
34
+
35
+ /**
36
+ * Set a sepecific empahasis level for differen fonts like subtitles
37
+ * @default 'default'
38
+ */
39
+ emphasis?: HeadingVariantProps['emphasis']
40
+
41
+ /**
42
+ * The content to display
43
+ */
44
+ children?: ReactNode
45
+ }
46
+
47
+ export const Heading = styled(
48
+ ark.h2,
49
+ heading
50
+ ) as React.ForwardRefExoticComponent<HeadingProps>
51
+
52
+ Heading.displayName = 'Heading'
@@ -0,0 +1,121 @@
1
+ import { render } from '@testing-library/react'
2
+ import { describe, expect, it } from 'vitest'
3
+
4
+ import { Text } from './index'
5
+
6
+ describe('Text', () => {
7
+ it('renders with default props', () => {
8
+ const { getByText } = render(<Text>Hello World</Text>)
9
+ const text = getByText('Hello World')
10
+ expect(text).toBeInTheDocument()
11
+ expect(text.tagName.toLowerCase()).toBe('span')
12
+ expect(text).toHaveClass('text', 'text--emphasis_default')
13
+ })
14
+
15
+ it('applies textStyle prop', () => {
16
+ const { getByText } = render(<Text textStyle='heading.1'>Large Text</Text>)
17
+ const text = getByText('Large Text')
18
+ expect(text).toHaveClass('textStyle_heading.1')
19
+ })
20
+
21
+ it('renders with custom element type', () => {
22
+ const { getByText } = render(<Text as='p'>Paragraph Text</Text>)
23
+ const element = getByText('Paragraph Text')
24
+ expect(element.tagName.toLowerCase()).toBe('p')
25
+ })
26
+
27
+ it('renders with different emphasis', () => {
28
+ const { rerender, getByText } = render(
29
+ <Text emphasis='strong'>Strong Text</Text>
30
+ )
31
+ let text = getByText('Strong Text')
32
+ expect(text).toHaveClass('text--emphasis_strong')
33
+
34
+ rerender(<Text emphasis='subtle'>Subtle Text</Text>)
35
+ text = getByText('Subtle Text')
36
+ expect(text).toHaveClass('text--emphasis_subtle')
37
+
38
+ rerender(<Text emphasis='subtler'>subtler Text</Text>)
39
+ text = getByText('subtler Text')
40
+ expect(text).toHaveClass('text--emphasis_subtler')
41
+
42
+ rerender(<Text emphasis='default'>Default Text</Text>)
43
+ text = getByText('Default Text')
44
+ expect(text).toHaveClass('text--emphasis_default')
45
+ })
46
+
47
+ it('renders with different color palettes', () => {
48
+ const { rerender, getByText } = render(
49
+ <Text colorPalette='accent'>Accent Text</Text>
50
+ )
51
+ let text = getByText('Accent Text')
52
+ expect(text).toHaveClass('color-palette_accent')
53
+
54
+ rerender(<Text colorPalette='brand'>Brand Text</Text>)
55
+ text = getByText('Brand Text')
56
+ expect(text).toHaveClass('color-palette_brand')
57
+
58
+ rerender(<Text colorPalette='success'>Success Text</Text>)
59
+ text = getByText('Success Text')
60
+ expect(text).toHaveClass('color-palette_success')
61
+
62
+ rerender(<Text colorPalette='warning'>Warning Text</Text>)
63
+ text = getByText('Warning Text')
64
+ expect(text).toHaveClass('color-palette_warning')
65
+
66
+ rerender(<Text colorPalette='danger'>Danger Text</Text>)
67
+ text = getByText('Danger Text')
68
+ expect(text).toHaveClass('color-palette_danger')
69
+ })
70
+
71
+ it('applies interactive styles', () => {
72
+ const { getByText } = render(<Text interactive>Interactive Text</Text>)
73
+ const text = getByText('Interactive Text')
74
+ expect(text).toHaveClass('text--interactive_true')
75
+ })
76
+
77
+ it('applies line clamp prop', () => {
78
+ const { getByText } = render(
79
+ <Text lineClamp={2}>Multi-line text that should be clamped</Text>
80
+ )
81
+ const text = getByText('Multi-line text that should be clamped')
82
+ expect(text).toHaveClass('lc_2')
83
+ })
84
+
85
+ it('forwards additional HTML attributes', () => {
86
+ const { getByTestId } = render(
87
+ <Text data-testid='text' title='tooltip' aria-label='Accessible text'>
88
+ Text with attributes
89
+ </Text>
90
+ )
91
+ const text = getByTestId('text')
92
+ expect(text).toHaveClass('text')
93
+ expect(text).toHaveAttribute('title', 'tooltip')
94
+ expect(text).toHaveAttribute('aria-label', 'Accessible text')
95
+ })
96
+
97
+ it('combines multiple props', () => {
98
+ const { getByText } = render(
99
+ <Text
100
+ textStyle='heading.2'
101
+ emphasis='strong'
102
+ colorPalette='accent'
103
+ interactive
104
+ lineClamp={2}
105
+ className='custom-class'
106
+ >
107
+ Styled Text
108
+ </Text>
109
+ )
110
+ const text = getByText('Styled Text')
111
+ expect(text).toHaveClass(
112
+ 'text',
113
+ 'text--emphasis_strong',
114
+ 'text--interactive_true',
115
+ 'textStyle_heading.2',
116
+ 'color-palette_accent',
117
+ 'lc_2',
118
+ 'custom-class'
119
+ )
120
+ })
121
+ })
@@ -0,0 +1,54 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ import { ark } from '@ark-ui/react/factory'
4
+
5
+ import type { ColorPalette } from '@oztix/roadie-core'
6
+ import { styled } from '@oztix/roadie-core/jsx'
7
+ import type { HTMLStyledProps } from '@oztix/roadie-core/jsx'
8
+ import { type TextVariantProps, text } from '@oztix/roadie-core/recipes'
9
+
10
+ /**
11
+ * Text component for displaying content with various styling options
12
+ */
13
+ export interface TextProps extends HTMLStyledProps<'span'> {
14
+ /**
15
+ * The visual emphasis of the text
16
+ * @default 'default'
17
+ */
18
+ emphasis?: TextVariantProps['emphasis']
19
+
20
+ /**
21
+ * Whether the text is interactive
22
+ * @default false
23
+ */
24
+ interactive?: TextVariantProps['interactive']
25
+
26
+ /**
27
+ * The HTML element to render the text as
28
+ * @default 'span'
29
+ */
30
+ as?: HTMLStyledProps<'span'>['as']
31
+
32
+ /**
33
+ * The color palette to use for the text
34
+ * @default 'neutral'
35
+ */
36
+ colorPalette?: ColorPalette
37
+
38
+ /**
39
+ * When true, the component will pass props to its child component
40
+ */
41
+ asChild?: boolean
42
+
43
+ /**
44
+ * The content to display
45
+ */
46
+ children?: ReactNode
47
+ }
48
+
49
+ export const Text = styled(
50
+ ark.span,
51
+ text
52
+ ) as React.ForwardRefExoticComponent<TextProps>
53
+
54
+ Text.displayName = 'Text'