@oztix/roadie-components 1.0.0 → 1.2.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.
- package/dist/Button.d.ts +18 -33
- package/dist/Button.js +1 -2
- package/dist/Button.js.map +1 -1
- package/dist/Code.js +1 -2
- package/dist/Code.js.map +1 -1
- package/dist/Container.d.ts +34 -0
- package/dist/Container.js +2 -0
- package/dist/Container.js.map +1 -0
- package/dist/Heading.d.ts +7 -2
- package/dist/Heading.js +1 -2
- package/dist/Heading.js.map +1 -1
- package/dist/Highlight.d.ts +44 -0
- package/dist/Highlight.js +3 -0
- package/dist/Highlight.js.map +1 -0
- package/dist/Mark.d.ts +29 -0
- package/dist/Mark.js +2 -0
- package/dist/Mark.js.map +1 -0
- package/dist/SpotIllustration.d.ts +79 -0
- package/dist/SpotIllustration.js +2 -0
- package/dist/SpotIllustration.js.map +1 -0
- package/dist/Text.d.ts +11 -5
- package/dist/Text.js +1 -2
- package/dist/Text.js.map +1 -1
- package/dist/View.js +1 -2
- package/dist/View.js.map +1 -1
- package/dist/_chunks/chunk-AZZHYO2A.js +3 -0
- package/dist/_chunks/chunk-AZZHYO2A.js.map +1 -0
- package/dist/_chunks/chunk-JOQJCXYF.js +2 -0
- package/dist/_chunks/chunk-JOQJCXYF.js.map +1 -0
- package/dist/_chunks/chunk-NMGF2AP6.js +2 -0
- package/dist/_chunks/chunk-NMGF2AP6.js.map +1 -0
- package/dist/_chunks/chunk-OH4JYS35.js +3 -0
- package/dist/_chunks/chunk-OH4JYS35.js.map +1 -0
- package/dist/_chunks/chunk-P5L5LN6E.js +1 -8
- package/dist/_chunks/chunk-P5L5LN6E.js.map +1 -1
- package/dist/_chunks/chunk-RJEJUZ3O.js +1 -6
- package/dist/_chunks/chunk-RJEJUZ3O.js.map +1 -1
- package/dist/_chunks/chunk-SUDUTP6A.js +3 -0
- package/dist/_chunks/chunk-SUDUTP6A.js.map +1 -0
- package/dist/_chunks/chunk-YNF56IUK.js +2 -0
- package/dist/_chunks/chunk-YNF56IUK.js.map +1 -0
- package/dist/_chunks/chunk-ZXS7U3VJ.js +2 -0
- package/dist/_chunks/chunk-ZXS7U3VJ.js.map +1 -0
- package/dist/hooks/index.d.ts +27 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.js +1 -6
- package/dist/index.js.map +1 -1
- package/package.json +33 -19
- package/src/components/Button/Button.tsx +12 -0
- package/src/components/Button/IconButton.test.tsx +234 -0
- package/src/components/Button/IconButton.tsx +14 -0
- package/src/components/Button/index.tsx +2 -48
- package/src/components/Container/Container.test.tsx +241 -0
- package/src/components/Container/index.tsx +34 -0
- package/src/components/Heading/index.tsx +1 -4
- package/src/components/Highlight/Highlight.test.tsx +113 -0
- package/src/components/Highlight/index.tsx +96 -0
- package/src/components/Mark/Mark.test.tsx +82 -0
- package/src/components/Mark/index.tsx +33 -0
- package/src/components/SpotIllustration/ArrowUpRight.tsx +9 -0
- package/src/components/SpotIllustration/CowboyHat.tsx +6 -0
- package/src/components/SpotIllustration/Cursor.tsx +6 -0
- package/src/components/SpotIllustration/FlowerSpiral.tsx +9 -0
- package/src/components/SpotIllustration/Football.tsx +6 -0
- package/src/components/SpotIllustration/Hand.tsx +6 -0
- package/src/components/SpotIllustration/Heart.tsx +6 -0
- package/src/components/SpotIllustration/HighFive.tsx +6 -0
- package/src/components/SpotIllustration/MapPin.tsx +6 -0
- package/src/components/SpotIllustration/NoteMusic.tsx +6 -0
- package/src/components/SpotIllustration/README.md +280 -0
- package/src/components/SpotIllustration/SpotIllustration.test.tsx +179 -0
- package/src/components/SpotIllustration/SpotIllustration.tsx +96 -0
- package/src/components/SpotIllustration/Ticket.tsx +6 -0
- package/src/components/SpotIllustration/WineGlass.tsx +6 -0
- package/src/components/SpotIllustration/createSpotIllustration.tsx +46 -0
- package/src/components/SpotIllustration/index.tsx +42 -0
- package/src/components/SpotIllustration/json/arrow-up-right.json +34 -0
- package/src/components/SpotIllustration/json/cowboy-hat.json +34 -0
- package/src/components/SpotIllustration/json/cursor.json +34 -0
- package/src/components/SpotIllustration/json/flower-spiral.json +38 -0
- package/src/components/SpotIllustration/json/football.json +46 -0
- package/src/components/SpotIllustration/json/hand.json +22 -0
- package/src/components/SpotIllustration/json/heart.json +26 -0
- package/src/components/SpotIllustration/json/high-five.json +62 -0
- package/src/components/SpotIllustration/json/map-pin.json +26 -0
- package/src/components/SpotIllustration/json/note-music.json +42 -0
- package/src/components/SpotIllustration/json/ticket.json +42 -0
- package/src/components/SpotIllustration/json/wine-glass.json +34 -0
- package/src/components/SpotIllustration/svgs/arrow-up-right.svg +9 -0
- package/src/components/SpotIllustration/svgs/cowboy-hat.svg +9 -0
- package/src/components/SpotIllustration/svgs/cursor.svg +9 -0
- package/src/components/SpotIllustration/svgs/flower-spiral.svg +10 -0
- package/src/components/SpotIllustration/svgs/football.svg +12 -0
- package/src/components/SpotIllustration/svgs/hand.svg +6 -0
- package/src/components/SpotIllustration/svgs/heart.svg +7 -0
- package/src/components/SpotIllustration/svgs/high-five.svg +16 -0
- package/src/components/SpotIllustration/svgs/map-pin.svg +7 -0
- package/src/components/SpotIllustration/svgs/note-music.svg +11 -0
- package/src/components/SpotIllustration/svgs/ticket.svg +11 -0
- package/src/components/SpotIllustration/svgs/wine-glass.svg +9 -0
- package/src/components/Text/Text.test.tsx +1 -1
- package/src/components/Text/index.tsx +4 -7
- package/src/components/index.ts +4 -1
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useColorMode.ts +37 -0
- package/src/index.tsx +4 -0
- package/dist/_chunks/chunk-6FIUWNC7.js +0 -9
- package/dist/_chunks/chunk-6FIUWNC7.js.map +0 -1
- package/dist/_chunks/chunk-GSK3G4DW.js +0 -9
- package/dist/_chunks/chunk-GSK3G4DW.js.map +0 -1
- package/dist/_chunks/chunk-VDMZIGT2.js +0 -10
- package/dist/_chunks/chunk-VDMZIGT2.js.map +0 -1
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { render } from '@testing-library/react'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import { Container } from './index'
|
|
5
|
+
|
|
6
|
+
describe('Container', () => {
|
|
7
|
+
it('renders with default props', () => {
|
|
8
|
+
const { getByTestId } = render(
|
|
9
|
+
<Container data-testid='container'>Content</Container>
|
|
10
|
+
)
|
|
11
|
+
const container = getByTestId('container')
|
|
12
|
+
expect(container).toBeInTheDocument()
|
|
13
|
+
expect(container.tagName.toLowerCase()).toBe('div')
|
|
14
|
+
expect(container).toHaveClass(
|
|
15
|
+
'd_flex',
|
|
16
|
+
'pos_relative',
|
|
17
|
+
'flex-d_column',
|
|
18
|
+
'flex-wrap_nowrap',
|
|
19
|
+
'ai_stretch',
|
|
20
|
+
'ac_flex-start',
|
|
21
|
+
'jc_flex-start',
|
|
22
|
+
'min-h_0',
|
|
23
|
+
'min-w_0',
|
|
24
|
+
'w_full',
|
|
25
|
+
'mx_auto',
|
|
26
|
+
'max-w_8xl'
|
|
27
|
+
)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('applies responsive padding by default', () => {
|
|
31
|
+
const { getByTestId } = render(
|
|
32
|
+
<Container data-testid='container'>Content</Container>
|
|
33
|
+
)
|
|
34
|
+
const container = getByTestId('container')
|
|
35
|
+
// Should have responsive padding classes
|
|
36
|
+
expect(container.className).toMatch(/px_/)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('renders with contain true by default (constrained width)', () => {
|
|
40
|
+
const { getByTestId } = render(
|
|
41
|
+
<Container data-testid='container'>Constrained Content</Container>
|
|
42
|
+
)
|
|
43
|
+
const container = getByTestId('container')
|
|
44
|
+
expect(container).toHaveClass('max-w_8xl')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('renders with contain=false (full width)', () => {
|
|
48
|
+
const { getByTestId } = render(
|
|
49
|
+
<Container contain={false} data-testid='container'>
|
|
50
|
+
Full Width Content
|
|
51
|
+
</Container>
|
|
52
|
+
)
|
|
53
|
+
const container = getByTestId('container')
|
|
54
|
+
expect(container).toHaveClass('max-w_full')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('renders with contain prop explicitly set to true', () => {
|
|
58
|
+
const { getByTestId } = render(
|
|
59
|
+
<Container contain data-testid='container'>
|
|
60
|
+
Constrained Content
|
|
61
|
+
</Container>
|
|
62
|
+
)
|
|
63
|
+
const container = getByTestId('container')
|
|
64
|
+
expect(container).toHaveClass('max-w_8xl')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('renders with different HTML elements', () => {
|
|
68
|
+
const elements: Array<'section' | 'article' | 'aside' | 'main'> = [
|
|
69
|
+
'section',
|
|
70
|
+
'article',
|
|
71
|
+
'aside',
|
|
72
|
+
'main'
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
elements.forEach((element) => {
|
|
76
|
+
const { rerender, getByTestId } = render(
|
|
77
|
+
<Container as={element} data-testid='container'>
|
|
78
|
+
{element} content
|
|
79
|
+
</Container>
|
|
80
|
+
)
|
|
81
|
+
const container = getByTestId('container')
|
|
82
|
+
expect(container.tagName.toLowerCase()).toBe(element)
|
|
83
|
+
expect(container).toHaveClass(
|
|
84
|
+
'd_flex',
|
|
85
|
+
'pos_relative',
|
|
86
|
+
'flex-d_column',
|
|
87
|
+
'flex-wrap_nowrap',
|
|
88
|
+
'ai_stretch',
|
|
89
|
+
'ac_flex-start',
|
|
90
|
+
'jc_flex-start',
|
|
91
|
+
'min-h_0',
|
|
92
|
+
'min-w_0',
|
|
93
|
+
'w_full',
|
|
94
|
+
'mx_auto'
|
|
95
|
+
)
|
|
96
|
+
rerender(<></>)
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('applies layout properties', () => {
|
|
101
|
+
const { getByTestId } = render(
|
|
102
|
+
<Container
|
|
103
|
+
data-testid='container'
|
|
104
|
+
display='inline-flex'
|
|
105
|
+
position='absolute'
|
|
106
|
+
flexDirection='row'
|
|
107
|
+
flexWrap='wrap'
|
|
108
|
+
alignItems='center'
|
|
109
|
+
alignContent='center'
|
|
110
|
+
justifyContent='center'
|
|
111
|
+
>
|
|
112
|
+
Styled Container
|
|
113
|
+
</Container>
|
|
114
|
+
)
|
|
115
|
+
const container = getByTestId('container')
|
|
116
|
+
expect(container).toHaveClass(
|
|
117
|
+
'd_inline-flex',
|
|
118
|
+
'pos_absolute',
|
|
119
|
+
'flex-d_row',
|
|
120
|
+
'flex-wrap_wrap',
|
|
121
|
+
'ai_center',
|
|
122
|
+
'ac_center',
|
|
123
|
+
'jc_center',
|
|
124
|
+
'min-h_0',
|
|
125
|
+
'min-w_0'
|
|
126
|
+
)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it('applies custom styles and attributes', () => {
|
|
130
|
+
const { getByTestId } = render(
|
|
131
|
+
<Container
|
|
132
|
+
data-testid='container'
|
|
133
|
+
backgroundColor='neutral.bg.subtle'
|
|
134
|
+
padding='200'
|
|
135
|
+
title='tooltip'
|
|
136
|
+
aria-label='Accessible container'
|
|
137
|
+
>
|
|
138
|
+
Custom Container
|
|
139
|
+
</Container>
|
|
140
|
+
)
|
|
141
|
+
const container = getByTestId('container')
|
|
142
|
+
expect(container).toHaveClass('bg-c_neutral.bg.subtle', 'p_200')
|
|
143
|
+
expect(container).toHaveAttribute('title', 'tooltip')
|
|
144
|
+
expect(container).toHaveAttribute('aria-label', 'Accessible container')
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('renders nested containers', () => {
|
|
148
|
+
const { getByTestId } = render(
|
|
149
|
+
<Container data-testid='parent'>
|
|
150
|
+
<Container data-testid='child'>Nested Content</Container>
|
|
151
|
+
</Container>
|
|
152
|
+
)
|
|
153
|
+
const parent = getByTestId('parent')
|
|
154
|
+
const child = getByTestId('child')
|
|
155
|
+
expect(parent).toContainElement(child)
|
|
156
|
+
expect(child).toHaveTextContent('Nested Content')
|
|
157
|
+
expect(parent).toHaveClass(
|
|
158
|
+
'd_flex',
|
|
159
|
+
'pos_relative',
|
|
160
|
+
'flex-d_column',
|
|
161
|
+
'flex-wrap_nowrap',
|
|
162
|
+
'ai_stretch',
|
|
163
|
+
'ac_flex-start',
|
|
164
|
+
'jc_flex-start',
|
|
165
|
+
'min-h_0',
|
|
166
|
+
'min-w_0',
|
|
167
|
+
'w_full',
|
|
168
|
+
'mx_auto'
|
|
169
|
+
)
|
|
170
|
+
expect(child).toHaveClass(
|
|
171
|
+
'd_flex',
|
|
172
|
+
'pos_relative',
|
|
173
|
+
'flex-d_column',
|
|
174
|
+
'flex-wrap_nowrap',
|
|
175
|
+
'ai_stretch',
|
|
176
|
+
'ac_flex-start',
|
|
177
|
+
'jc_flex-start',
|
|
178
|
+
'min-h_0',
|
|
179
|
+
'min-w_0',
|
|
180
|
+
'w_full',
|
|
181
|
+
'mx_auto'
|
|
182
|
+
)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('combines multiple props including contain', () => {
|
|
186
|
+
const { getByTestId } = render(
|
|
187
|
+
<Container
|
|
188
|
+
as='section'
|
|
189
|
+
contain
|
|
190
|
+
display='grid'
|
|
191
|
+
gap='200'
|
|
192
|
+
padding='400'
|
|
193
|
+
backgroundColor='neutral.bg.subtle'
|
|
194
|
+
className='custom-class'
|
|
195
|
+
data-testid='container'
|
|
196
|
+
>
|
|
197
|
+
Combined styles
|
|
198
|
+
</Container>
|
|
199
|
+
)
|
|
200
|
+
const container = getByTestId('container')
|
|
201
|
+
expect(container.tagName.toLowerCase()).toBe('section')
|
|
202
|
+
expect(container).toHaveClass(
|
|
203
|
+
'd_grid',
|
|
204
|
+
'gap_200',
|
|
205
|
+
'p_400',
|
|
206
|
+
'bg-c_neutral.bg.subtle',
|
|
207
|
+
'max-w_8xl',
|
|
208
|
+
'custom-class'
|
|
209
|
+
)
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
it('can override default padding', () => {
|
|
213
|
+
const { getByTestId } = render(
|
|
214
|
+
<Container data-testid='container' px='800'>
|
|
215
|
+
Custom Padding
|
|
216
|
+
</Container>
|
|
217
|
+
)
|
|
218
|
+
const container = getByTestId('container')
|
|
219
|
+
expect(container).toHaveClass('px_800')
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('can override default width', () => {
|
|
223
|
+
const { getByTestId } = render(
|
|
224
|
+
<Container data-testid='container' width='auto'>
|
|
225
|
+
Custom Width
|
|
226
|
+
</Container>
|
|
227
|
+
)
|
|
228
|
+
const container = getByTestId('container')
|
|
229
|
+
expect(container).toHaveClass('w_auto')
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
it('can set contain=false for full width', () => {
|
|
233
|
+
const { getByTestId } = render(
|
|
234
|
+
<Container contain={false} data-testid='container'>
|
|
235
|
+
Full Width
|
|
236
|
+
</Container>
|
|
237
|
+
)
|
|
238
|
+
const container = getByTestId('container')
|
|
239
|
+
expect(container).toHaveClass('max-w_full')
|
|
240
|
+
})
|
|
241
|
+
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Container as ContainerPattern } from '@oztix/roadie-core/jsx'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A foundational layout component that provides a centered container with responsive padding
|
|
5
|
+
* and a constrained max-width by default. Perfect for page layouts and content sections.
|
|
6
|
+
*
|
|
7
|
+
* By default, Container has:
|
|
8
|
+
* - Centered content with `mx: auto`
|
|
9
|
+
* - Responsive horizontal padding (300 on mobile, 400 on tablet, 600 on desktop)
|
|
10
|
+
* - Max-width constraint of 7xl (80rem)
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* // Basic usage - constrained width by default
|
|
15
|
+
* <Container>
|
|
16
|
+
* <h1>Page Title</h1>
|
|
17
|
+
* <p>Content with readable max-width</p>
|
|
18
|
+
* </Container>
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* // Full-width container
|
|
24
|
+
* <Container contain={false}>
|
|
25
|
+
* <h1>Hero Section</h1>
|
|
26
|
+
* <p>This content spans the full width</p>
|
|
27
|
+
* </Container>
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export const Container = ContainerPattern
|
|
31
|
+
|
|
32
|
+
export type ContainerProps = React.ComponentProps<typeof ContainerPattern>
|
|
33
|
+
|
|
34
|
+
Container.displayName = 'Container'
|
|
@@ -44,9 +44,6 @@ export interface HeadingProps extends HTMLStyledProps<'h2'> {
|
|
|
44
44
|
children?: ReactNode
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
export const Heading = styled(
|
|
48
|
-
ark.h2,
|
|
49
|
-
heading
|
|
50
|
-
) as React.ForwardRefExoticComponent<HeadingProps>
|
|
47
|
+
export const Heading = styled(ark.h2, heading)
|
|
51
48
|
|
|
52
49
|
Heading.displayName = 'Heading'
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { render } from '@testing-library/react'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import { Highlight } from './index'
|
|
5
|
+
|
|
6
|
+
describe('Highlight', () => {
|
|
7
|
+
it('renders with text prop', () => {
|
|
8
|
+
const { container } = render(<Highlight text='Hello World' query='World' />)
|
|
9
|
+
expect(container).toHaveTextContent('Hello World')
|
|
10
|
+
const mark = container.querySelector('mark')
|
|
11
|
+
expect(mark).toBeInTheDocument()
|
|
12
|
+
expect(mark).toHaveTextContent('World')
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('highlights only matching text when query is provided', () => {
|
|
16
|
+
const { container } = render(
|
|
17
|
+
<Highlight text='The quick brown fox' query='quick' />
|
|
18
|
+
)
|
|
19
|
+
expect(container).toHaveTextContent('The quick brown fox')
|
|
20
|
+
const mark = container.querySelector('mark')
|
|
21
|
+
expect(mark).toHaveTextContent('quick')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('highlights multiple matches when matchAll is true', () => {
|
|
25
|
+
const { container } = render(
|
|
26
|
+
<Highlight text='foo bar foo baz' query='foo' matchAll />
|
|
27
|
+
)
|
|
28
|
+
const marks = container.querySelectorAll('mark')
|
|
29
|
+
expect(marks).toHaveLength(2)
|
|
30
|
+
marks.forEach((mark: Element) => {
|
|
31
|
+
expect(mark).toHaveTextContent('foo')
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('highlights with case insensitive matching by default', () => {
|
|
36
|
+
const { container } = render(<Highlight text='Hello World' query='hello' />)
|
|
37
|
+
const mark = container.querySelector('mark')
|
|
38
|
+
expect(mark).toHaveTextContent('Hello')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('respects case when ignoreCase is false', () => {
|
|
42
|
+
const { container } = render(
|
|
43
|
+
<Highlight text='Hello World' query='hello' ignoreCase={false} />
|
|
44
|
+
)
|
|
45
|
+
const mark = container.querySelector('mark')
|
|
46
|
+
expect(mark).toBeNull()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('highlights multiple queries', () => {
|
|
50
|
+
const { container } = render(
|
|
51
|
+
<Highlight text='The quick brown fox' query={['quick', 'fox']} />
|
|
52
|
+
)
|
|
53
|
+
const marks = container.querySelectorAll('mark')
|
|
54
|
+
expect(marks).toHaveLength(2)
|
|
55
|
+
expect(marks[0]).toHaveTextContent('quick')
|
|
56
|
+
expect(marks[1]).toHaveTextContent('fox')
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('applies default color palette', () => {
|
|
60
|
+
const { container } = render(
|
|
61
|
+
<Highlight text='Highlighted text' query='Highlighted' />
|
|
62
|
+
)
|
|
63
|
+
const mark = container.querySelector('mark')
|
|
64
|
+
expect(mark).toHaveClass('color-palette_information')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('applies custom color palette', () => {
|
|
68
|
+
const { container } = render(
|
|
69
|
+
<Highlight
|
|
70
|
+
text='Highlighted text'
|
|
71
|
+
query='Highlighted'
|
|
72
|
+
colorPalette='success'
|
|
73
|
+
/>
|
|
74
|
+
)
|
|
75
|
+
const mark = container.querySelector('mark')
|
|
76
|
+
expect(mark).toHaveClass('color-palette_success')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('matches exact words when exactMatch is true', () => {
|
|
80
|
+
const { container } = render(
|
|
81
|
+
<Highlight text='foo foobar' query='foo' exactMatch />
|
|
82
|
+
)
|
|
83
|
+
const marks = container.querySelectorAll('mark')
|
|
84
|
+
expect(marks).toHaveLength(1)
|
|
85
|
+
expect(marks[0]).toHaveTextContent('foo')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('forwards additional HTML attributes', () => {
|
|
89
|
+
const { container } = render(
|
|
90
|
+
<Highlight
|
|
91
|
+
text='Hello'
|
|
92
|
+
query='Hello'
|
|
93
|
+
data-testid='highlight'
|
|
94
|
+
title='tooltip'
|
|
95
|
+
/>
|
|
96
|
+
)
|
|
97
|
+
const mark = container.querySelector('mark')
|
|
98
|
+
expect(mark).toHaveAttribute('data-testid', 'highlight')
|
|
99
|
+
expect(mark).toHaveAttribute('title', 'tooltip')
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('applies custom className', () => {
|
|
103
|
+
const { container } = render(
|
|
104
|
+
<Highlight
|
|
105
|
+
text='Highlighted text'
|
|
106
|
+
query='Highlighted'
|
|
107
|
+
className='custom-class'
|
|
108
|
+
/>
|
|
109
|
+
)
|
|
110
|
+
const mark = container.querySelector('mark')
|
|
111
|
+
expect(mark).toHaveClass('mark', 'custom-class')
|
|
112
|
+
})
|
|
113
|
+
})
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Fragment, type ReactElement } from 'react'
|
|
4
|
+
|
|
5
|
+
import { useHighlight } from '@ark-ui/react/highlight'
|
|
6
|
+
|
|
7
|
+
import type { ColorPalette } from '@oztix/roadie-core'
|
|
8
|
+
import type { HTMLStyledProps } from '@oztix/roadie-core/jsx'
|
|
9
|
+
|
|
10
|
+
import { Mark } from '../Mark'
|
|
11
|
+
|
|
12
|
+
// Re-export Ark UI types and hook for advanced use cases
|
|
13
|
+
export { useHighlight } from '@ark-ui/react/highlight'
|
|
14
|
+
export type { HighlightChunk, UseHighlightProps } from '@ark-ui/react/highlight'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Highlight component for highlighting substrings within text
|
|
18
|
+
*/
|
|
19
|
+
export interface HighlightProps
|
|
20
|
+
extends Omit<HTMLStyledProps<'mark'>, 'children'> {
|
|
21
|
+
/**
|
|
22
|
+
* The text content to display and potentially highlight
|
|
23
|
+
*/
|
|
24
|
+
text: string
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The query string(s) to highlight within the text
|
|
28
|
+
*/
|
|
29
|
+
query: string | string[]
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Whether to match whole words only
|
|
33
|
+
* @default false
|
|
34
|
+
*/
|
|
35
|
+
exactMatch?: boolean
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Whether to ignore case while matching
|
|
39
|
+
* @default true
|
|
40
|
+
*/
|
|
41
|
+
ignoreCase?: boolean
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Whether to match multiple instances of the query
|
|
45
|
+
* @default true
|
|
46
|
+
*/
|
|
47
|
+
matchAll?: boolean
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The color palette to use for the highlight
|
|
51
|
+
* @default 'information'
|
|
52
|
+
*/
|
|
53
|
+
colorPalette?: ColorPalette
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const Highlight = ({
|
|
57
|
+
text,
|
|
58
|
+
query,
|
|
59
|
+
exactMatch = false,
|
|
60
|
+
ignoreCase = true,
|
|
61
|
+
matchAll = true,
|
|
62
|
+
colorPalette = 'information',
|
|
63
|
+
...props
|
|
64
|
+
}: HighlightProps): ReactElement => {
|
|
65
|
+
// Fast path: if query is empty, just return the text
|
|
66
|
+
const isQueryEmpty =
|
|
67
|
+
!query || (Array.isArray(query) && query.length === 0) || query === ''
|
|
68
|
+
|
|
69
|
+
if (isQueryEmpty) {
|
|
70
|
+
return <>{text}</>
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const chunks = useHighlight({
|
|
74
|
+
query,
|
|
75
|
+
text,
|
|
76
|
+
exactMatch,
|
|
77
|
+
ignoreCase,
|
|
78
|
+
matchAll
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<>
|
|
83
|
+
{chunks.map((chunk, index) =>
|
|
84
|
+
chunk.match ? (
|
|
85
|
+
<Mark key={index} colorPalette={colorPalette} {...props}>
|
|
86
|
+
{chunk.text}
|
|
87
|
+
</Mark>
|
|
88
|
+
) : (
|
|
89
|
+
<Fragment key={index}>{chunk.text}</Fragment>
|
|
90
|
+
)
|
|
91
|
+
)}
|
|
92
|
+
</>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
Highlight.displayName = 'Highlight'
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { render } from '@testing-library/react'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import { Mark } from './index'
|
|
5
|
+
|
|
6
|
+
describe('Mark', () => {
|
|
7
|
+
it('renders with default props', () => {
|
|
8
|
+
const { container } = render(<Mark>Marked text</Mark>)
|
|
9
|
+
const mark = container.querySelector('mark')
|
|
10
|
+
expect(mark).toBeInTheDocument()
|
|
11
|
+
expect(mark).toHaveTextContent('Marked text')
|
|
12
|
+
expect(mark).toHaveClass('mark')
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('applies color palette when provided', () => {
|
|
16
|
+
const { container } = render(
|
|
17
|
+
<Mark colorPalette='information'>Marked text</Mark>
|
|
18
|
+
)
|
|
19
|
+
const mark = container.querySelector('mark')
|
|
20
|
+
expect(mark).toHaveClass('color-palette_information')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('applies custom color palette', () => {
|
|
24
|
+
const { container } = render(
|
|
25
|
+
<Mark colorPalette='success'>Marked text</Mark>
|
|
26
|
+
)
|
|
27
|
+
const mark = container.querySelector('mark')
|
|
28
|
+
expect(mark).toHaveClass('color-palette_success')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('applies custom className', () => {
|
|
32
|
+
const { container } = render(
|
|
33
|
+
<Mark className='custom-class'>Marked text</Mark>
|
|
34
|
+
)
|
|
35
|
+
const mark = container.querySelector('mark')
|
|
36
|
+
expect(mark).toHaveClass('mark', 'custom-class')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('forwards additional HTML attributes', () => {
|
|
40
|
+
const { container } = render(
|
|
41
|
+
<Mark data-testid='mark' title='tooltip'>
|
|
42
|
+
Marked text
|
|
43
|
+
</Mark>
|
|
44
|
+
)
|
|
45
|
+
const mark = container.querySelector('mark')
|
|
46
|
+
expect(mark).toHaveAttribute('data-testid', 'mark')
|
|
47
|
+
expect(mark).toHaveAttribute('title', 'tooltip')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('renders with different color palettes', () => {
|
|
51
|
+
const { rerender, container } = render(
|
|
52
|
+
<Mark colorPalette='accent'>Accent</Mark>
|
|
53
|
+
)
|
|
54
|
+
let mark = container.querySelector('mark')
|
|
55
|
+
expect(mark).toHaveClass('color-palette_accent')
|
|
56
|
+
|
|
57
|
+
rerender(<Mark colorPalette='brand'>Brand</Mark>)
|
|
58
|
+
mark = container.querySelector('mark')
|
|
59
|
+
expect(mark).toHaveClass('color-palette_brand')
|
|
60
|
+
|
|
61
|
+
rerender(<Mark colorPalette='warning'>Warning</Mark>)
|
|
62
|
+
mark = container.querySelector('mark')
|
|
63
|
+
expect(mark).toHaveClass('color-palette_warning')
|
|
64
|
+
|
|
65
|
+
rerender(<Mark colorPalette='danger'>Danger</Mark>)
|
|
66
|
+
mark = container.querySelector('mark')
|
|
67
|
+
expect(mark).toHaveClass('color-palette_danger')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('renders children correctly', () => {
|
|
71
|
+
const { container } = render(
|
|
72
|
+
<Mark>
|
|
73
|
+
This is <strong>important</strong> text
|
|
74
|
+
</Mark>
|
|
75
|
+
)
|
|
76
|
+
const mark = container.querySelector('mark')
|
|
77
|
+
expect(mark).toBeInTheDocument()
|
|
78
|
+
expect(mark?.textContent).toBe('This is important text')
|
|
79
|
+
const strong = mark?.querySelector('strong')
|
|
80
|
+
expect(strong).toHaveTextContent('important')
|
|
81
|
+
})
|
|
82
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
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 { mark } from '@oztix/roadie-core/recipes'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Mark component for highlighting text content
|
|
12
|
+
*/
|
|
13
|
+
export interface MarkProps extends HTMLStyledProps<'mark'> {
|
|
14
|
+
/**
|
|
15
|
+
* The color palette to use for the mark
|
|
16
|
+
* @default 'information'
|
|
17
|
+
*/
|
|
18
|
+
colorPalette?: ColorPalette
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* When true, the component will pass props to its child component
|
|
22
|
+
*/
|
|
23
|
+
asChild?: boolean
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The content to mark
|
|
27
|
+
*/
|
|
28
|
+
children?: ReactNode
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const Mark = styled(ark.mark, mark)
|
|
32
|
+
|
|
33
|
+
Mark.displayName = 'Mark'
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Generated file - do not edit directly
|
|
2
|
+
import { createSpotIllustration } from './createSpotIllustration'
|
|
3
|
+
import arrowuprightData from './json/arrow-up-right.json'
|
|
4
|
+
|
|
5
|
+
export const ArrowUpRight = createSpotIllustration(
|
|
6
|
+
'ArrowUpRight',
|
|
7
|
+
arrowuprightData
|
|
8
|
+
)
|
|
9
|
+
export type ArrowUpRightProps = React.ComponentPropsWithRef<typeof ArrowUpRight>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Generated file - do not edit directly
|
|
2
|
+
import { createSpotIllustration } from './createSpotIllustration'
|
|
3
|
+
import cowboyhatData from './json/cowboy-hat.json'
|
|
4
|
+
|
|
5
|
+
export const CowboyHat = createSpotIllustration('CowboyHat', cowboyhatData)
|
|
6
|
+
export type CowboyHatProps = React.ComponentPropsWithRef<typeof CowboyHat>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Generated file - do not edit directly
|
|
2
|
+
import { createSpotIllustration } from './createSpotIllustration'
|
|
3
|
+
import cursorData from './json/cursor.json'
|
|
4
|
+
|
|
5
|
+
export const Cursor = createSpotIllustration('Cursor', cursorData)
|
|
6
|
+
export type CursorProps = React.ComponentPropsWithRef<typeof Cursor>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Generated file - do not edit directly
|
|
2
|
+
import { createSpotIllustration } from './createSpotIllustration'
|
|
3
|
+
import flowerspiralData from './json/flower-spiral.json'
|
|
4
|
+
|
|
5
|
+
export const FlowerSpiral = createSpotIllustration(
|
|
6
|
+
'FlowerSpiral',
|
|
7
|
+
flowerspiralData
|
|
8
|
+
)
|
|
9
|
+
export type FlowerSpiralProps = React.ComponentPropsWithRef<typeof FlowerSpiral>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Generated file - do not edit directly
|
|
2
|
+
import { createSpotIllustration } from './createSpotIllustration'
|
|
3
|
+
import footballData from './json/football.json'
|
|
4
|
+
|
|
5
|
+
export const Football = createSpotIllustration('Football', footballData)
|
|
6
|
+
export type FootballProps = React.ComponentPropsWithRef<typeof Football>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Generated file - do not edit directly
|
|
2
|
+
import { createSpotIllustration } from './createSpotIllustration'
|
|
3
|
+
import handData from './json/hand.json'
|
|
4
|
+
|
|
5
|
+
export const Hand = createSpotIllustration('Hand', handData)
|
|
6
|
+
export type HandProps = React.ComponentPropsWithRef<typeof Hand>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Generated file - do not edit directly
|
|
2
|
+
import { createSpotIllustration } from './createSpotIllustration'
|
|
3
|
+
import heartData from './json/heart.json'
|
|
4
|
+
|
|
5
|
+
export const Heart = createSpotIllustration('Heart', heartData)
|
|
6
|
+
export type HeartProps = React.ComponentPropsWithRef<typeof Heart>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Generated file - do not edit directly
|
|
2
|
+
import { createSpotIllustration } from './createSpotIllustration'
|
|
3
|
+
import highfiveData from './json/high-five.json'
|
|
4
|
+
|
|
5
|
+
export const HighFive = createSpotIllustration('HighFive', highfiveData)
|
|
6
|
+
export type HighFiveProps = React.ComponentPropsWithRef<typeof HighFive>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Generated file - do not edit directly
|
|
2
|
+
import { createSpotIllustration } from './createSpotIllustration'
|
|
3
|
+
import mappinData from './json/map-pin.json'
|
|
4
|
+
|
|
5
|
+
export const MapPin = createSpotIllustration('MapPin', mappinData)
|
|
6
|
+
export type MapPinProps = React.ComponentPropsWithRef<typeof MapPin>
|