@playfast/reform-better-hub-ui-primitives 0.2.2 → 0.3.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/README.md +9 -9
- package/package.json +23 -23
- package/src/components/agent-icon.tsx +16 -16
- package/src/components/autocomplete.tsx +166 -162
- package/src/components/badge.tsx +46 -45
- package/src/components/button.tsx +56 -63
- package/src/components/combobox.tsx +233 -228
- package/src/components/command.tsx +135 -139
- package/src/components/dialog.tsx +106 -98
- package/src/components/dropdown-menu.tsx +118 -115
- package/src/components/github-background.tsx +214 -216
- package/src/components/halftone-background.tsx +164 -155
- package/src/components/halftone-config.ts +8 -8
- package/src/components/input-group.tsx +149 -147
- package/src/components/input.tsx +17 -17
- package/src/components/live-duration.tsx +70 -64
- package/src/components/logo.tsx +9 -13
- package/src/components/popover.tsx +60 -62
- package/src/components/resize-handle.tsx +73 -75
- package/src/components/sheet.tsx +120 -112
- package/src/components/textarea.tsx +15 -15
- package/src/components/time-ago.tsx +64 -62
- package/src/components/tooltip.tsx +48 -42
- package/src/foundation/styles.css +6 -4
- package/src/index.ts +21 -21
|
@@ -1,173 +1,175 @@
|
|
|
1
|
-
import type * as React from
|
|
2
|
-
import type { ComponentProps } from
|
|
3
|
-
import { cva, type VariantProps } from
|
|
4
|
-
import type { ClassProp } from
|
|
1
|
+
import type * as React from 'react'
|
|
2
|
+
import type { ComponentProps } from 'react'
|
|
3
|
+
import { cva, type VariantProps } from 'class-variance-authority'
|
|
4
|
+
import type { ClassProp } from 'class-variance-authority/types'
|
|
5
5
|
|
|
6
|
-
import { cn } from
|
|
7
|
-
import { Button } from
|
|
8
|
-
import { Input } from
|
|
9
|
-
import { Textarea } from
|
|
6
|
+
import { cn } from '../foundation/cn'
|
|
7
|
+
import { Button } from './button'
|
|
8
|
+
import { Input } from './input'
|
|
9
|
+
import { Textarea } from './textarea'
|
|
10
10
|
|
|
11
|
-
function InputGroup({ className, ...props }: ComponentProps<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
11
|
+
function InputGroup({ className, ...props }: ComponentProps<'div'>): React.JSX.Element {
|
|
12
|
+
return (
|
|
13
|
+
<div
|
|
14
|
+
data-slot="input-group"
|
|
15
|
+
role="group"
|
|
16
|
+
className={cn(
|
|
17
|
+
'group/input-group relative flex w-full items-center rounded-md border border-input shadow-xs transition-[color,box-shadow] outline-none dark:bg-input/30',
|
|
18
|
+
'h-9 min-w-0 has-[>textarea]:h-auto',
|
|
19
|
+
'has-[>[data-align=inline-start]]:[&>input]:pl-2',
|
|
20
|
+
'has-[>[data-align=inline-end]]:[&>input]:pr-2',
|
|
21
|
+
'has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3',
|
|
22
|
+
'has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3',
|
|
23
|
+
'has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50',
|
|
24
|
+
'has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40',
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
className,
|
|
27
|
+
)}
|
|
28
|
+
{...props}
|
|
29
|
+
/>
|
|
30
|
+
)
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
type InputGroupAddonVariantsExternalApi = (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
) => string
|
|
34
|
+
props?:
|
|
35
|
+
| ({
|
|
36
|
+
align?: 'inline-start' | 'inline-end' | 'block-start' | 'block-end' | undefined
|
|
37
|
+
} & ClassProp)
|
|
38
|
+
| undefined,
|
|
39
|
+
) => string
|
|
40
40
|
|
|
41
41
|
const inputGroupAddonVariants: InputGroupAddonVariantsExternalApi = cva(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
},
|
|
60
|
-
);
|
|
42
|
+
"flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",
|
|
43
|
+
{
|
|
44
|
+
variants: {
|
|
45
|
+
align: {
|
|
46
|
+
'inline-start': 'order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]',
|
|
47
|
+
'inline-end': 'order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]',
|
|
48
|
+
'block-start':
|
|
49
|
+
'order-first w-full justify-start px-3 pt-3 group-has-[>input]/input-group:pt-2.5 [.border-b]:pb-3',
|
|
50
|
+
'block-end':
|
|
51
|
+
'order-last w-full justify-start px-3 pb-3 group-has-[>input]/input-group:pb-2.5 [.border-t]:pt-3',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
defaultVariants: {
|
|
55
|
+
align: 'inline-start',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
)
|
|
61
59
|
|
|
62
60
|
function InputGroupAddon({
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}: ComponentProps<
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
61
|
+
className,
|
|
62
|
+
align = 'inline-start',
|
|
63
|
+
...props
|
|
64
|
+
}: ComponentProps<'div'> & VariantProps<typeof inputGroupAddonVariants>): React.JSX.Element {
|
|
65
|
+
return (
|
|
66
|
+
<div
|
|
67
|
+
role="group"
|
|
68
|
+
data-slot="input-group-addon"
|
|
69
|
+
data-align={align}
|
|
70
|
+
className={cn(inputGroupAddonVariants({ align }), className)}
|
|
71
|
+
onClick={(event) => {
|
|
72
|
+
const target = event.target
|
|
73
|
+
if (target instanceof HTMLElement && target.closest('button')) {
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
event.currentTarget.parentElement?.querySelector('input')?.focus()
|
|
77
|
+
}}
|
|
78
|
+
{...props}
|
|
79
|
+
/>
|
|
80
|
+
)
|
|
83
81
|
}
|
|
84
82
|
|
|
85
83
|
type InputGroupButtonVariantsExternalApi = (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
) => string
|
|
84
|
+
props?:
|
|
85
|
+
| ({
|
|
86
|
+
size?: 'xs' | 'sm' | 'icon-xs' | 'icon-sm' | undefined
|
|
87
|
+
} & ClassProp)
|
|
88
|
+
| undefined,
|
|
89
|
+
) => string
|
|
92
90
|
|
|
93
91
|
const inputGroupButtonVariants: InputGroupButtonVariantsExternalApi = cva(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
92
|
+
'flex items-center gap-2 text-sm shadow-none',
|
|
93
|
+
{
|
|
94
|
+
variants: {
|
|
95
|
+
size: {
|
|
96
|
+
xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-2 has-[>svg]:px-2 [&>svg:not([class*='size-'])]:size-3.5",
|
|
97
|
+
sm: 'h-8 gap-1.5 rounded-md px-2.5 has-[>svg]:px-2.5',
|
|
98
|
+
'icon-xs': 'size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0',
|
|
99
|
+
'icon-sm': 'size-8 p-0 has-[>svg]:p-0',
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
defaultVariants: {
|
|
103
|
+
size: 'xs',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
)
|
|
108
107
|
|
|
109
108
|
function InputGroupButton({
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}: Omit<ComponentProps<typeof Button>,
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
109
|
+
className,
|
|
110
|
+
type = 'button',
|
|
111
|
+
variant = 'ghost',
|
|
112
|
+
size = 'xs',
|
|
113
|
+
...props
|
|
114
|
+
}: Omit<ComponentProps<typeof Button>, 'size'> &
|
|
115
|
+
VariantProps<typeof inputGroupButtonVariants>): React.JSX.Element {
|
|
116
|
+
return (
|
|
117
|
+
<Button
|
|
118
|
+
type={type}
|
|
119
|
+
data-size={size}
|
|
120
|
+
variant={variant}
|
|
121
|
+
className={cn(inputGroupButtonVariants({ size }), className)}
|
|
122
|
+
{...props}
|
|
123
|
+
/>
|
|
124
|
+
)
|
|
126
125
|
}
|
|
127
126
|
|
|
128
|
-
function InputGroupText({ className, ...props }: ComponentProps<
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
127
|
+
function InputGroupText({ className, ...props }: ComponentProps<'span'>): React.JSX.Element {
|
|
128
|
+
return (
|
|
129
|
+
<span
|
|
130
|
+
className={cn(
|
|
131
|
+
"flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
|
132
|
+
className,
|
|
133
|
+
)}
|
|
134
|
+
{...props}
|
|
135
|
+
/>
|
|
136
|
+
)
|
|
138
137
|
}
|
|
139
138
|
|
|
140
|
-
function InputGroupInput({ className, ...props }: ComponentProps<
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
139
|
+
function InputGroupInput({ className, ...props }: ComponentProps<'input'>): React.JSX.Element {
|
|
140
|
+
return (
|
|
141
|
+
<Input
|
|
142
|
+
data-slot="input-group-control"
|
|
143
|
+
className={cn(
|
|
144
|
+
'flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent',
|
|
145
|
+
className,
|
|
146
|
+
)}
|
|
147
|
+
{...props}
|
|
148
|
+
/>
|
|
149
|
+
)
|
|
151
150
|
}
|
|
152
151
|
|
|
153
|
-
function InputGroupTextarea({
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
152
|
+
function InputGroupTextarea({
|
|
153
|
+
className,
|
|
154
|
+
...props
|
|
155
|
+
}: ComponentProps<'textarea'>): React.JSX.Element {
|
|
156
|
+
return (
|
|
157
|
+
<Textarea
|
|
158
|
+
data-slot="input-group-control"
|
|
159
|
+
className={cn(
|
|
160
|
+
'flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent',
|
|
161
|
+
className,
|
|
162
|
+
)}
|
|
163
|
+
{...props}
|
|
164
|
+
/>
|
|
165
|
+
)
|
|
164
166
|
}
|
|
165
167
|
|
|
166
168
|
export {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
169
|
+
InputGroup,
|
|
170
|
+
InputGroupAddon,
|
|
171
|
+
InputGroupButton,
|
|
172
|
+
InputGroupText,
|
|
173
|
+
InputGroupInput,
|
|
174
|
+
InputGroupTextarea,
|
|
175
|
+
}
|
package/src/components/input.tsx
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import type * as React from
|
|
2
|
-
import type { ComponentProps } from 'react'
|
|
1
|
+
import type * as React from 'react'
|
|
2
|
+
import type { ComponentProps } from 'react'
|
|
3
3
|
|
|
4
|
-
import { cn } from '../foundation/cn'
|
|
4
|
+
import { cn } from '../foundation/cn'
|
|
5
5
|
|
|
6
6
|
function Input({ className, type, ...props }: ComponentProps<'input'>): React.JSX.Element {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
7
|
+
return (
|
|
8
|
+
<input
|
|
9
|
+
type={type}
|
|
10
|
+
data-slot="input"
|
|
11
|
+
className={cn(
|
|
12
|
+
'h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30',
|
|
13
|
+
'focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50',
|
|
14
|
+
'aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40',
|
|
15
|
+
className,
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
)
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
export { Input }
|
|
22
|
+
export { Input }
|
|
@@ -1,86 +1,92 @@
|
|
|
1
|
-
import { DateTime, Option } from
|
|
2
|
-
import type * as React from
|
|
3
|
-
import { useState, useEffect } from
|
|
1
|
+
import { DateTime, Option } from 'effect'
|
|
2
|
+
import type * as React from 'react'
|
|
3
|
+
import { useState, useEffect } from 'react'
|
|
4
4
|
|
|
5
|
-
const MS_PER_SECOND = 1000
|
|
6
|
-
const SECONDS_PER_HOUR = 3600
|
|
7
|
-
const SECONDS_PER_MINUTE = 60
|
|
8
|
-
const TICK_INTERVAL_MS = 1000
|
|
5
|
+
const MS_PER_SECOND = 1000
|
|
6
|
+
const SECONDS_PER_HOUR = 3600
|
|
7
|
+
const SECONDS_PER_MINUTE = 60
|
|
8
|
+
const TICK_INTERVAL_MS = 1000
|
|
9
9
|
|
|
10
10
|
interface FormatDurationInputExternalApi {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
startedAt: string | null
|
|
12
|
+
completedAt: string | null
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
function formatDuration({ startedAt, completedAt }: FormatDurationInputExternalApi): string {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
16
|
+
if (!startedAt) {
|
|
17
|
+
return ''
|
|
18
|
+
}
|
|
19
|
+
const start = DateTime.unsafeMake(startedAt)
|
|
20
|
+
const end = completedAt ? DateTime.unsafeMake(completedAt) : DateTime.unsafeNow()
|
|
21
|
+
const elapsedMs = DateTime.toEpochMillis(end) - DateTime.toEpochMillis(start)
|
|
22
|
+
const totalSeconds = Math.max(0, Math.floor(elapsedMs / MS_PER_SECOND))
|
|
23
|
+
const hours = Math.floor(totalSeconds / SECONDS_PER_HOUR)
|
|
24
|
+
const minutes = Math.floor((totalSeconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE)
|
|
25
|
+
const seconds = totalSeconds % SECONDS_PER_MINUTE
|
|
26
|
+
if (hours > 0) {
|
|
27
|
+
return `${hours}h ${minutes}m`
|
|
28
|
+
}
|
|
29
|
+
if (minutes > 0) {
|
|
30
|
+
return `${minutes}m ${seconds}s`
|
|
31
|
+
}
|
|
32
|
+
return `${seconds}s`
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// shared tick: one interval drives all LiveDuration subscribers
|
|
36
|
-
const callbacks = new Set<() => void>()
|
|
36
|
+
const callbacks = new Set<() => void>()
|
|
37
37
|
const tickState: { intervalId: Option.Option<ReturnType<typeof setInterval>> } = {
|
|
38
|
-
|
|
39
|
-
}
|
|
38
|
+
intervalId: Option.none(),
|
|
39
|
+
}
|
|
40
40
|
|
|
41
41
|
function subscribeLiveTick(callback: () => void): () => void {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
42
|
+
callbacks.add(callback)
|
|
43
|
+
if (Option.isNone(tickState.intervalId)) {
|
|
44
|
+
tickState.intervalId = Option.some(
|
|
45
|
+
// oxlint-disable-next-line reform-rules/no-set-timeout-interval -- browser-timer seam: shared render ticker for React, no Effect runtime here
|
|
46
|
+
setInterval(() => {
|
|
47
|
+
callbacks.forEach((cb) => cb())
|
|
48
|
+
}, TICK_INTERVAL_MS),
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
return () => {
|
|
52
|
+
callbacks.delete(callback)
|
|
53
|
+
if (callbacks.size === 0 && Option.isSome(tickState.intervalId)) {
|
|
54
|
+
clearInterval(tickState.intervalId.value)
|
|
55
|
+
tickState.intervalId = Option.none()
|
|
56
|
+
}
|
|
57
|
+
}
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
interface LiveDurationPropsExternalApi {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
startedAt: string | null
|
|
62
|
+
completedAt?: string | null
|
|
63
|
+
className?: string
|
|
62
64
|
}
|
|
63
65
|
|
|
64
|
-
export function LiveDuration({
|
|
65
|
-
|
|
66
|
+
export function LiveDuration({
|
|
67
|
+
startedAt,
|
|
68
|
+
completedAt,
|
|
69
|
+
className,
|
|
70
|
+
}: LiveDurationPropsExternalApi): React.ReactNode {
|
|
71
|
+
const [, setTick] = useState(0)
|
|
66
72
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (!startedAt || completedAt != null) {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
setTick(0)
|
|
78
|
+
return subscribeLiveTick(() => setTick((prev) => prev + 1))
|
|
79
|
+
}, [startedAt, completedAt])
|
|
74
80
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
81
|
+
if (!startedAt) {
|
|
82
|
+
return null
|
|
83
|
+
}
|
|
78
84
|
|
|
79
|
-
|
|
85
|
+
const formatted = formatDuration({ startedAt, completedAt: completedAt ?? null })
|
|
80
86
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
return (
|
|
88
|
+
<span className={className} suppressHydrationWarning>
|
|
89
|
+
{formatted}
|
|
90
|
+
</span>
|
|
91
|
+
)
|
|
86
92
|
}
|
package/src/components/logo.tsx
CHANGED
|
@@ -1,22 +1,18 @@
|
|
|
1
|
-
import type * as React from
|
|
2
|
-
import { cn } from '../foundation/cn'
|
|
1
|
+
import type * as React from 'react'
|
|
2
|
+
import { cn } from '../foundation/cn'
|
|
3
3
|
|
|
4
4
|
interface LogoPropsExternalApi {
|
|
5
|
-
|
|
5
|
+
className?: string
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export function Logo({ className }: LogoPropsExternalApi): React.JSX.Element {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
return (
|
|
10
|
+
<span className={cn('font-mono text-sm font-medium tracking-tight', className)}>
|
|
11
|
+
BETTER-HUB.
|
|
12
|
+
</span>
|
|
13
|
+
)
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export function LogoMark({ className }: LogoPropsExternalApi): React.JSX.Element {
|
|
17
|
-
|
|
18
|
-
<span className={cn("font-mono text-sm font-bold tracking-tight", className)}>
|
|
19
|
-
b.
|
|
20
|
-
</span>
|
|
21
|
-
);
|
|
17
|
+
return <span className={cn('font-mono text-sm font-bold tracking-tight', className)}>b.</span>
|
|
22
18
|
}
|