@uniweb/kit 0.1.1

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 (37) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +345 -0
  3. package/package.json +47 -0
  4. package/src/components/Asset/Asset.jsx +161 -0
  5. package/src/components/Asset/index.js +1 -0
  6. package/src/components/Disclaimer/Disclaimer.jsx +198 -0
  7. package/src/components/Disclaimer/index.js +1 -0
  8. package/src/components/FileLogo/FileLogo.jsx +148 -0
  9. package/src/components/FileLogo/index.js +1 -0
  10. package/src/components/Icon/Icon.jsx +214 -0
  11. package/src/components/Icon/index.js +1 -0
  12. package/src/components/Image/Image.jsx +194 -0
  13. package/src/components/Image/index.js +1 -0
  14. package/src/components/Link/Link.jsx +261 -0
  15. package/src/components/Link/index.js +1 -0
  16. package/src/components/Media/Media.jsx +322 -0
  17. package/src/components/Media/index.js +1 -0
  18. package/src/components/MediaIcon/MediaIcon.jsx +95 -0
  19. package/src/components/MediaIcon/index.js +1 -0
  20. package/src/components/SafeHtml/SafeHtml.jsx +93 -0
  21. package/src/components/SafeHtml/index.js +1 -0
  22. package/src/components/Section/Render.jsx +245 -0
  23. package/src/components/Section/Section.jsx +131 -0
  24. package/src/components/Section/index.js +3 -0
  25. package/src/components/Section/renderers/Alert.jsx +101 -0
  26. package/src/components/Section/renderers/Code.jsx +70 -0
  27. package/src/components/Section/renderers/Details.jsx +77 -0
  28. package/src/components/Section/renderers/Divider.jsx +42 -0
  29. package/src/components/Section/renderers/Table.jsx +55 -0
  30. package/src/components/Section/renderers/index.js +11 -0
  31. package/src/components/Text/Text.jsx +207 -0
  32. package/src/components/Text/index.js +14 -0
  33. package/src/hooks/index.js +1 -0
  34. package/src/hooks/useWebsite.js +77 -0
  35. package/src/index.js +69 -0
  36. package/src/styles/index.css +8 -0
  37. package/src/utils/index.js +104 -0
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Render Component
3
+ *
4
+ * Orchestrates rendering of content blocks within a Section.
5
+ * Dispatches to appropriate renderers based on content type.
6
+ *
7
+ * @module @uniweb/kit/Section/Render
8
+ */
9
+
10
+ import React from 'react'
11
+ import { cn } from '../../utils/index.js'
12
+ import { SafeHtml } from '../SafeHtml/index.js'
13
+ import { Image } from '../Image/index.js'
14
+ import { Media } from '../Media/index.js'
15
+ import { Link } from '../Link/index.js'
16
+ import { Code } from './renderers/Code.jsx'
17
+ import { Alert } from './renderers/Alert.jsx'
18
+ import { Table } from './renderers/Table.jsx'
19
+ import { Details } from './renderers/Details.jsx'
20
+ import { Divider } from './renderers/Divider.jsx'
21
+
22
+ /**
23
+ * Extract text content from a node
24
+ */
25
+ function extractText(node) {
26
+ if (!node) return ''
27
+ if (typeof node === 'string') return node
28
+ if (node.text) return node.text
29
+ if (node.content) {
30
+ return node.content.map(extractText).join('')
31
+ }
32
+ return ''
33
+ }
34
+
35
+ /**
36
+ * Generate ID from heading text
37
+ */
38
+ function generateId(text) {
39
+ return text
40
+ .toLowerCase()
41
+ .replace(/[^a-z0-9]+/g, '-')
42
+ .replace(/^-|-$/g, '')
43
+ }
44
+
45
+ /**
46
+ * Render a list (ordered or unordered)
47
+ */
48
+ function renderList(items, ordered = false) {
49
+ const Tag = ordered ? 'ol' : 'ul'
50
+ const listClass = ordered ? 'list-decimal' : 'list-disc'
51
+
52
+ return (
53
+ <Tag className={cn('pl-6 space-y-1', listClass)}>
54
+ {items?.map((item, i) => (
55
+ <li key={i}>
56
+ {item.content?.map((child, j) => (
57
+ <RenderNode key={j} node={child} />
58
+ ))}
59
+ </li>
60
+ ))}
61
+ </Tag>
62
+ )
63
+ }
64
+
65
+ /**
66
+ * Render a single content node
67
+ */
68
+ function RenderNode({ node, ...props }) {
69
+ if (!node) return null
70
+
71
+ const { type, attrs, content } = node
72
+
73
+ switch (type) {
74
+ case 'paragraph': {
75
+ const html = extractText(node)
76
+ if (!html) return null
77
+ return <p><SafeHtml value={html} as="span" /></p>
78
+ }
79
+
80
+ case 'heading': {
81
+ const level = attrs?.level || 1
82
+ const text = extractText(node)
83
+ const id = generateId(text)
84
+ const Tag = `h${Math.min(level, 6)}`
85
+
86
+ return (
87
+ <Tag id={id} className="scroll-mt-20">
88
+ {text}
89
+ </Tag>
90
+ )
91
+ }
92
+
93
+ case 'image': {
94
+ const src = attrs?.src || ''
95
+ const alt = attrs?.alt || ''
96
+ const caption = attrs?.caption || ''
97
+
98
+ return (
99
+ <figure className="my-4">
100
+ <Image src={src} alt={alt} className="rounded-lg" />
101
+ {caption && (
102
+ <figcaption className="mt-2 text-sm text-gray-500 text-center">
103
+ {caption}
104
+ </figcaption>
105
+ )}
106
+ </figure>
107
+ )
108
+ }
109
+
110
+ case 'video': {
111
+ const src = attrs?.src || ''
112
+ return <Media src={src} className="my-4 rounded-lg overflow-hidden" />
113
+ }
114
+
115
+ case 'codeBlock': {
116
+ const language = attrs?.language || 'plaintext'
117
+ const code = extractText(node)
118
+ return <Code content={code} language={language} className="my-4" />
119
+ }
120
+
121
+ case 'warning':
122
+ case 'alert': {
123
+ const alertType = attrs?.type || 'info'
124
+ const alertContent = content?.map(extractText).join('') || ''
125
+ return <Alert type={alertType} content={alertContent} className="my-4" />
126
+ }
127
+
128
+ case 'blockquote': {
129
+ return (
130
+ <blockquote className="border-l-4 border-gray-300 pl-4 italic text-gray-600 my-4">
131
+ {content?.map((child, i) => (
132
+ <RenderNode key={i} node={child} />
133
+ ))}
134
+ </blockquote>
135
+ )
136
+ }
137
+
138
+ case 'bulletList': {
139
+ return renderList(content, false)
140
+ }
141
+
142
+ case 'orderedList': {
143
+ return renderList(content, true)
144
+ }
145
+
146
+ case 'table': {
147
+ return <Table content={content} className="my-4" />
148
+ }
149
+
150
+ case 'details': {
151
+ const summary = attrs?.summary || 'Details'
152
+ const detailsContent = content?.map(extractText).join('') || ''
153
+ return (
154
+ <Details
155
+ summary={summary}
156
+ content={detailsContent}
157
+ open={attrs?.open}
158
+ className="my-4"
159
+ />
160
+ )
161
+ }
162
+
163
+ case 'horizontalRule':
164
+ case 'divider': {
165
+ return <Divider type={attrs?.type} className="my-6" />
166
+ }
167
+
168
+ case 'button': {
169
+ const href = attrs?.href || '#'
170
+ const label = extractText(node) || attrs?.label || 'Button'
171
+ return (
172
+ <Link
173
+ to={href}
174
+ className="inline-block px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors my-2"
175
+ >
176
+ {label}
177
+ </Link>
178
+ )
179
+ }
180
+
181
+ case 'text': {
182
+ // Handle inline marks (bold, italic, etc.)
183
+ let text = node.text || ''
184
+
185
+ if (node.marks) {
186
+ node.marks.forEach((mark) => {
187
+ switch (mark.type) {
188
+ case 'bold':
189
+ case 'strong':
190
+ text = `<strong>${text}</strong>`
191
+ break
192
+ case 'italic':
193
+ case 'em':
194
+ text = `<em>${text}</em>`
195
+ break
196
+ case 'code':
197
+ text = `<code class="px-1 py-0.5 bg-gray-100 rounded text-sm">${text}</code>`
198
+ break
199
+ case 'link':
200
+ text = `<a href="${mark.attrs?.href || '#'}" class="text-blue-600 hover:underline">${text}</a>`
201
+ break
202
+ }
203
+ })
204
+ }
205
+
206
+ return <SafeHtml value={text} as="span" />
207
+ }
208
+
209
+ default:
210
+ // Try to render children if they exist
211
+ if (content && Array.isArray(content)) {
212
+ return (
213
+ <>
214
+ {content.map((child, i) => (
215
+ <RenderNode key={i} node={child} />
216
+ ))}
217
+ </>
218
+ )
219
+ }
220
+ return null
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Render - Content block renderer
226
+ *
227
+ * @param {Object} props
228
+ * @param {Array|Object} props.content - Content to render (array of nodes or single node)
229
+ * @param {string} [props.className] - Additional CSS classes
230
+ */
231
+ export function Render({ content, className, ...props }) {
232
+ if (!content) return null
233
+
234
+ const nodes = Array.isArray(content) ? content : [content]
235
+
236
+ return (
237
+ <div className={cn('space-y-4', className)} {...props}>
238
+ {nodes.map((node, i) => (
239
+ <RenderNode key={i} node={node} />
240
+ ))}
241
+ </div>
242
+ )
243
+ }
244
+
245
+ export default Render
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Section Component
3
+ *
4
+ * Rich content section renderer for Uniweb pages.
5
+ * Handles content parsing, layout, and rendering.
6
+ *
7
+ * @module @uniweb/kit/Section
8
+ */
9
+
10
+ import React from 'react'
11
+ import { cn } from '../../utils/index.js'
12
+ import { useWebsite } from '../../hooks/useWebsite.js'
13
+ import { Render } from './Render.jsx'
14
+
15
+ /**
16
+ * Width presets
17
+ */
18
+ const WIDTH_CLASSES = {
19
+ sm: 'max-w-2xl',
20
+ md: 'max-w-4xl',
21
+ lg: 'max-w-5xl',
22
+ xl: 'max-w-6xl',
23
+ '2xl': 'max-w-7xl',
24
+ full: 'max-w-none'
25
+ }
26
+
27
+ /**
28
+ * Column layouts
29
+ */
30
+ const COLUMN_CLASSES = {
31
+ '1': 'grid-cols-1',
32
+ '2': 'grid-cols-1 md:grid-cols-2',
33
+ '3': 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
34
+ '4': 'grid-cols-1 md:grid-cols-2 lg:grid-cols-4'
35
+ }
36
+
37
+ /**
38
+ * Padding presets
39
+ */
40
+ const PADDING_CLASSES = {
41
+ none: '',
42
+ sm: 'py-8',
43
+ md: 'py-12',
44
+ lg: 'py-16',
45
+ xl: 'py-24'
46
+ }
47
+
48
+ /**
49
+ * Section - Rich content section
50
+ *
51
+ * @param {Object} props
52
+ * @param {Object} [props.block] - Block object from content
53
+ * @param {Object|Array} [props.content] - Content to render
54
+ * @param {string} [props.width='lg'] - Content width: sm, md, lg, xl, 2xl, full
55
+ * @param {string} [props.columns='1'] - Column layout: 1, 2, 3, 4
56
+ * @param {string} [props.padding='lg'] - Vertical padding: none, sm, md, lg, xl
57
+ * @param {string} [props.as='section'] - HTML element to render as
58
+ * @param {string} [props.className] - Additional CSS classes
59
+ * @param {React.ReactNode} [props.children] - Child elements
60
+ *
61
+ * @example
62
+ * <Section content={blockContent} width="lg" padding="md" />
63
+ *
64
+ * @example
65
+ * <Section width="xl" columns="2" className="bg-gray-50">
66
+ * <div>Column 1</div>
67
+ * <div>Column 2</div>
68
+ * </Section>
69
+ */
70
+ export function Section({
71
+ block,
72
+ content,
73
+ width = 'lg',
74
+ columns = '1',
75
+ padding = 'lg',
76
+ as: Component = 'section',
77
+ className,
78
+ children,
79
+ ...props
80
+ }) {
81
+ const { website } = useWebsite()
82
+
83
+ // Get properties from block if provided
84
+ const blockProps = block?.getBlockProperties?.() || {}
85
+ const resolvedWidth = blockProps.width || width
86
+ const resolvedColumns = blockProps.columns || columns
87
+ const resolvedPadding = blockProps.vertical_padding || padding
88
+
89
+ // Get content from block if not provided directly
90
+ let resolvedContent = content
91
+
92
+ if (!resolvedContent && block) {
93
+ // Get parsed content from block
94
+ resolvedContent = block.parsedContent || block.content
95
+
96
+ // If it's a ProseMirror doc, get the content array
97
+ if (resolvedContent?.type === 'doc') {
98
+ resolvedContent = resolvedContent.content
99
+ }
100
+ }
101
+
102
+ // Build classes
103
+ const widthClass = WIDTH_CLASSES[resolvedWidth] || WIDTH_CLASSES.lg
104
+ const columnClass = COLUMN_CLASSES[resolvedColumns] || COLUMN_CLASSES['1']
105
+ const paddingClass = PADDING_CLASSES[resolvedPadding] || PADDING_CLASSES.lg
106
+
107
+ return (
108
+ <Component
109
+ className={cn(paddingClass, className)}
110
+ {...props}
111
+ >
112
+ <div className={cn('mx-auto px-4 sm:px-6 lg:px-8', widthClass)}>
113
+ {/* Content grid */}
114
+ {(resolvedContent || children) && (
115
+ <div className={cn(
116
+ columns !== '1' && 'grid gap-8',
117
+ columns !== '1' && columnClass
118
+ )}>
119
+ {children || (
120
+ <div className="prose prose-gray max-w-none">
121
+ <Render content={resolvedContent} />
122
+ </div>
123
+ )}
124
+ </div>
125
+ )}
126
+ </div>
127
+ </Component>
128
+ )
129
+ }
130
+
131
+ export default Section
@@ -0,0 +1,3 @@
1
+ export { Section, default } from './Section.jsx'
2
+ export { Render } from './Render.jsx'
3
+ export * from './renderers/index.js'
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Alert/Warning Renderer
3
+ *
4
+ * Renders alert boxes for warnings, info, success, and error messages.
5
+ *
6
+ * @module @uniweb/kit/Section/renderers/Alert
7
+ */
8
+
9
+ import React from 'react'
10
+ import { cn } from '../../../utils/index.js'
11
+ import { SafeHtml } from '../../SafeHtml/index.js'
12
+
13
+ /**
14
+ * Alert type configurations
15
+ */
16
+ const ALERT_STYLES = {
17
+ info: {
18
+ container: 'bg-blue-50 border-blue-200 text-blue-800',
19
+ icon: (
20
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
21
+ ),
22
+ iconColor: 'text-blue-500'
23
+ },
24
+ success: {
25
+ container: 'bg-green-50 border-green-200 text-green-800',
26
+ icon: (
27
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
28
+ ),
29
+ iconColor: 'text-green-500'
30
+ },
31
+ warning: {
32
+ container: 'bg-yellow-50 border-yellow-200 text-yellow-800',
33
+ icon: (
34
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
35
+ ),
36
+ iconColor: 'text-yellow-500'
37
+ },
38
+ danger: {
39
+ container: 'bg-red-50 border-red-200 text-red-800',
40
+ icon: (
41
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
42
+ ),
43
+ iconColor: 'text-red-500'
44
+ },
45
+ error: {
46
+ container: 'bg-red-50 border-red-200 text-red-800',
47
+ icon: (
48
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
49
+ ),
50
+ iconColor: 'text-red-500'
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Alert - Alert/warning box
56
+ *
57
+ * @param {Object} props
58
+ * @param {string} [props.type='info'] - Alert type: info, success, warning, danger, error
59
+ * @param {string|React.ReactNode} props.content - Alert content
60
+ * @param {string} [props.title] - Optional title
61
+ * @param {string} [props.className] - Additional CSS classes
62
+ */
63
+ export function Alert({ type = 'info', content, title, className, ...props }) {
64
+ const styles = ALERT_STYLES[type] || ALERT_STYLES.info
65
+
66
+ return (
67
+ <div
68
+ className={cn(
69
+ 'flex gap-3 rounded-lg border p-4',
70
+ styles.container,
71
+ className
72
+ )}
73
+ role="alert"
74
+ {...props}
75
+ >
76
+ {/* Icon */}
77
+ <div className={cn('flex-shrink-0', styles.iconColor)}>
78
+ <svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
79
+ {styles.icon}
80
+ </svg>
81
+ </div>
82
+
83
+ {/* Content */}
84
+ <div className="flex-1">
85
+ {title && (
86
+ <h4 className="font-medium mb-1">{title}</h4>
87
+ )}
88
+ {typeof content === 'string' ? (
89
+ <SafeHtml value={content} className="text-sm" />
90
+ ) : (
91
+ <div className="text-sm">{content}</div>
92
+ )}
93
+ </div>
94
+ </div>
95
+ )
96
+ }
97
+
98
+ // Alias for backwards compatibility
99
+ export const Warning = Alert
100
+
101
+ export default Alert
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Code Block Renderer
3
+ *
4
+ * Renders syntax-highlighted code blocks.
5
+ * Uses CSS classes for highlighting (bring your own Prism/Highlight.js CSS).
6
+ *
7
+ * @module @uniweb/kit/Section/renderers/Code
8
+ */
9
+
10
+ import React, { useEffect, useRef } from 'react'
11
+ import { cn } from '../../../utils/index.js'
12
+
13
+ /**
14
+ * Attempt to highlight code using Prism if available
15
+ */
16
+ function highlightCode(code, language, element) {
17
+ if (typeof window !== 'undefined' && window.Prism && element) {
18
+ try {
19
+ const grammar = window.Prism.languages[language]
20
+ if (grammar) {
21
+ element.innerHTML = window.Prism.highlight(code, grammar, language)
22
+ return true
23
+ }
24
+ } catch (e) {
25
+ console.warn('[Code] Prism highlighting failed:', e)
26
+ }
27
+ }
28
+ return false
29
+ }
30
+
31
+ /**
32
+ * Code - Syntax highlighted code block
33
+ *
34
+ * @param {Object} props
35
+ * @param {string} props.content - Code content
36
+ * @param {string} [props.language='plaintext'] - Programming language
37
+ * @param {string} [props.className] - Additional CSS classes
38
+ */
39
+ export function Code({ content, language = 'plaintext', className, ...props }) {
40
+ const codeRef = useRef(null)
41
+
42
+ // Normalize language
43
+ const lang = language?.toLowerCase() || 'plaintext'
44
+
45
+ // Try to highlight on mount
46
+ useEffect(() => {
47
+ if (codeRef.current && content) {
48
+ highlightCode(content, lang, codeRef.current)
49
+ }
50
+ }, [content, lang])
51
+
52
+ return (
53
+ <pre
54
+ className={cn(
55
+ 'overflow-x-auto rounded-lg bg-gray-900 p-4 text-sm',
56
+ className
57
+ )}
58
+ {...props}
59
+ >
60
+ <code
61
+ ref={codeRef}
62
+ className={`language-${lang} text-gray-100`}
63
+ >
64
+ {content}
65
+ </code>
66
+ </pre>
67
+ )
68
+ }
69
+
70
+ export default Code
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Details/Collapsible Renderer
3
+ *
4
+ * Renders collapsible content sections.
5
+ *
6
+ * @module @uniweb/kit/Section/renderers/Details
7
+ */
8
+
9
+ import React, { useState } from 'react'
10
+ import { cn } from '../../../utils/index.js'
11
+ import { SafeHtml } from '../../SafeHtml/index.js'
12
+
13
+ /**
14
+ * Details - Collapsible section
15
+ *
16
+ * @param {Object} props
17
+ * @param {string} props.summary - Summary/title text
18
+ * @param {string|React.ReactNode} props.content - Collapsible content
19
+ * @param {boolean} [props.open=false] - Initially open
20
+ * @param {string} [props.className] - Additional CSS classes
21
+ */
22
+ export function Details({
23
+ summary,
24
+ content,
25
+ open = false,
26
+ className,
27
+ ...props
28
+ }) {
29
+ const [isOpen, setIsOpen] = useState(open)
30
+
31
+ return (
32
+ <div
33
+ className={cn(
34
+ 'border border-gray-200 rounded-lg overflow-hidden',
35
+ className
36
+ )}
37
+ {...props}
38
+ >
39
+ {/* Summary/Toggle */}
40
+ <button
41
+ className={cn(
42
+ 'w-full flex items-center justify-between px-4 py-3',
43
+ 'text-left font-medium text-gray-900 bg-gray-50',
44
+ 'hover:bg-gray-100 transition-colors'
45
+ )}
46
+ onClick={() => setIsOpen(!isOpen)}
47
+ aria-expanded={isOpen}
48
+ >
49
+ <span>{summary}</span>
50
+ <svg
51
+ className={cn(
52
+ 'w-5 h-5 text-gray-500 transition-transform',
53
+ isOpen && 'rotate-180'
54
+ )}
55
+ fill="none"
56
+ viewBox="0 0 24 24"
57
+ stroke="currentColor"
58
+ >
59
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
60
+ </svg>
61
+ </button>
62
+
63
+ {/* Content */}
64
+ {isOpen && (
65
+ <div className="px-4 py-3 border-t border-gray-200">
66
+ {typeof content === 'string' ? (
67
+ <SafeHtml value={content} className="prose prose-sm" />
68
+ ) : (
69
+ content
70
+ )}
71
+ </div>
72
+ )}
73
+ </div>
74
+ )
75
+ }
76
+
77
+ export default Details
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Divider Renderer
3
+ *
4
+ * Renders horizontal dividers.
5
+ *
6
+ * @module @uniweb/kit/Section/renderers/Divider
7
+ */
8
+
9
+ import React from 'react'
10
+ import { cn } from '../../../utils/index.js'
11
+
12
+ /**
13
+ * Divider - Horizontal divider
14
+ *
15
+ * @param {Object} props
16
+ * @param {string} [props.type='hr'] - Divider type: 'hr' or 'dots'
17
+ * @param {string} [props.className] - Additional CSS classes
18
+ */
19
+ export function Divider({ type = 'hr', className, ...props }) {
20
+ if (type === 'dots') {
21
+ return (
22
+ <div
23
+ className={cn('flex justify-center gap-2 py-4', className)}
24
+ role="separator"
25
+ {...props}
26
+ >
27
+ <span className="w-2 h-2 bg-gray-300 rounded-full" />
28
+ <span className="w-2 h-2 bg-gray-300 rounded-full" />
29
+ <span className="w-2 h-2 bg-gray-300 rounded-full" />
30
+ </div>
31
+ )
32
+ }
33
+
34
+ return (
35
+ <hr
36
+ className={cn('border-gray-200 my-6', className)}
37
+ {...props}
38
+ />
39
+ )
40
+ }
41
+
42
+ export default Divider