Skip to content

Commit bb42068

Browse files
committed
add posthog
1 parent 5651e6d commit bb42068

File tree

21 files changed

+1068
-402
lines changed

21 files changed

+1068
-402
lines changed
Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,60 @@
1-
import type * as React from "react"
1+
import * as React from "react"
2+
import { decode } from "blurhash"
3+
import queryString from "query-string"
4+
5+
import { defaultBlurHash, merge, srcWhitelist } from "@element/shared"
26

37
type Fit = "cover" | "contain" | "fill" | "inside" | "outside"
48

5-
interface Props extends React.ComponentPropsWithoutRef<"img"> {
9+
export interface OptimizedImageProps extends Omit<React.ComponentPropsWithoutRef<"img">, "placeholder" | "srcSet"> {
610
height: number
711
width: number
812
alt: string
913
quality?: number
1014
fit?: Fit
15+
placeholder?: string | null
16+
srcSet?: number[]
1117
}
1218

13-
export const transformImageSrc = (
14-
src: string | undefined | null,
15-
options: { width: number; height: number; quality?: number; fit?: Fit },
16-
) =>
17-
src
18-
? "/api/image?src=" +
19-
encodeURIComponent(src) +
20-
`&width=${options.width || ""}&height=${options.height || ""}&quality=${options.quality || ""}&fit=${
21-
options.fit || "cover"
22-
}`
23-
: undefined
24-
25-
export function OptimizedImage({ src, quality, fit, ...props }: Props) {
19+
export const OptimizedImage = React.forwardRef<HTMLImageElement, OptimizedImageProps>(function _OptimizedImage(
20+
{ src, quality, srcSet, placeholder, fit, ...props }: OptimizedImageProps,
21+
ref,
22+
) {
23+
// increase the width and height so quality is higher
2624
const newSrc = transformImageSrc(src, { width: props.width, height: props.height, quality, fit })
27-
return <img {...props} alt={props.alt} src={newSrc} />
25+
return (
26+
<div className={merge("relative overflow-hidden", props.className)}>
27+
<BlurCanvas blurHash={placeholder || defaultBlurHash} />
28+
<img ref={ref} {...props} className="relative h-full w-full rounded-[inherit] object-cover" alt={props.alt} src={newSrc} />
29+
</div>
30+
)
31+
})
32+
33+
export function transformImageSrc(
34+
src: string | undefined | null,
35+
options: { width: number; height?: number; quality?: number; fit?: Fit },
36+
) {
37+
if (!src) return undefined
38+
39+
if (!srcWhitelist.some((s) => src.startsWith(s))) return src
40+
const searchParams = queryString.stringify({ src, ...options })
41+
return "/api/image?" + searchParams
42+
}
43+
44+
interface BlurCanvasProps {
45+
blurHash: string
46+
}
47+
48+
function BlurCanvas(props: BlurCanvasProps) {
49+
const ref = React.useRef<HTMLCanvasElement>(null)
50+
React.useEffect(() => {
51+
if (!ref.current) return
52+
const pixels = decode(props.blurHash, 32, 32)
53+
const ctx = ref.current.getContext("2d")
54+
if (!ctx) return
55+
const imageData = new ImageData(pixels, 32, 32)
56+
ctx.putImageData(imageData, 0, 0)
57+
}, [props.blurHash])
58+
59+
return <canvas ref={ref} width={32} height={32} className="absolute -z-[1] h-full w-full rounded-[inherit]" />
2860
}
Lines changed: 23 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,31 @@
1-
import { merge } from "@element/shared"
2-
import * as RAvatar from "@radix-ui/react-avatar"
3-
import { cva, type VariantProps } from "class-variance-authority"
1+
"use client"
2+
3+
import { User2 } from "lucide-react"
44

5-
export const avatarStyles = cva("center rounded-full capitalize", {
6-
variants: {
7-
size: {
8-
xs: "sq-5 text-xs",
9-
sm: "sq-8 text-sm",
10-
md: "sq-10 text-md",
11-
lg: "sq-12 text-lg",
12-
},
13-
},
14-
defaultVariants: {
15-
size: "md",
16-
},
17-
})
5+
import { merge } from "@element/shared"
186

19-
export type AvatarProps = VariantProps<typeof avatarStyles>
7+
import type { OptimizedImageProps } from "../OptimisedImage"
8+
import { OptimizedImage } from "../OptimisedImage"
209

21-
interface Props extends AvatarProps, RAvatar.AvatarProps {
22-
name: string
23-
src?: string | null | undefined
10+
interface Props extends Omit<OptimizedImageProps, "height" | "width" | "alt"> {
11+
size?: number
2412
}
2513

26-
export function Avatar({ size, src, name, ...props }: Props) {
27-
const initials = name
28-
.split(" ")
29-
.map((n) => n[0])
30-
.join("")
14+
export function Avatar({ size = 100, src, ...props }: Props) {
15+
if (!src)
16+
return (
17+
<div className={merge("center rounded-full bg-gray-50 dark:bg-gray-700", props.className)}>
18+
<User2 size={16} />
19+
</div>
20+
)
3121
return (
32-
<RAvatar.Root className={merge(avatarStyles({ size }), props.className)}>
33-
<RAvatar.Image className="h-full w-full rounded-[inherit] object-cover" src={src || undefined} alt="avatar" />
34-
<RAvatar.Fallback
35-
className="center bg-primary-700 h-full w-full rounded-[inherit] object-cover text-xs font-semibold text-white"
36-
delayMs={600}
37-
>
38-
{initials}
39-
</RAvatar.Fallback>
40-
</RAvatar.Root>
22+
<OptimizedImage
23+
src={src}
24+
width={size}
25+
height={size}
26+
alt="avatar"
27+
{...props}
28+
className={merge("rounded-full", props.className)}
29+
/>
4130
)
4231
}

apps/web/app/components/ui/Inputs.tsx

Lines changed: 60 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
import * as React from "react"
2-
import { merge } from "@element/shared"
32
import { cva, type VariantProps } from "class-variance-authority"
43

4+
import { merge } from "@element/shared"
5+
56
export const inputStyles = cva(
6-
"focus:border-primary-500 rounded-xs focus:ring-primary-500 block w-full border text-base text-black placeholder-gray-500 ring-0 transition-colors focus:bg-transparent focus:ring-2 focus:ring-transparent dark:text-white",
7+
"focus:border-primary-500 dark:focus:border-primary-500 focus:ring-primary-500 rounded-xs block w-full border text-base text-black placeholder-gray-500 ring-0 transition-colors placeholder:font-thin focus:bg-transparent focus:ring-2 focus:ring-transparent read-only:focus:ring-transparent dark:text-white",
78
{
89
variants: {
910
variant: {
1011
solid: "border-transparent bg-black/5 hover:bg-black/10 dark:bg-white/5 dark:hover:bg-white/10",
11-
outline: "border-black/10 bg-transparent hover:border-black/40 dark:border-white/10 dark:hover:border-white/20",
12+
outline: "border-black/10 bg-transparent hover:border-black/90 dark:border-white/10 dark:hover:border-white/20",
1213
ghost: "border-transparent bg-transparent hover:border-black/10 dark:hover:border-white/10",
1314
},
1415
size: {
1516
xs: "px-2 py-1 text-xs",
16-
sm: "px-3 py-1.5 text-sm",
17-
md: "text-md px-4 py-2",
17+
sm: "px-3 py-1 text-sm",
18+
md: "px-4 py-1 text-base",
1819
lg: "px-5 py-3 text-lg",
1920
},
2021
},
@@ -29,9 +30,9 @@ export const inputSizeStyles = cva("", {
2930
variants: {
3031
size: {
3132
xs: "h-7",
32-
sm: "h-9",
33-
md: "h-11",
34-
lg: "h-12",
33+
sm: "h-8",
34+
md: "h-9",
35+
lg: "h-11",
3536
},
3637
},
3738
defaultVariants: {
@@ -48,16 +49,39 @@ export interface InputProps
4849
InputStyleProps,
4950
InputSizeStyleProps {
5051
name?: string
52+
leftElement?: React.ReactNode
53+
rightElement?: React.ReactNode
54+
ref?: React.Ref<HTMLInputElement>
5155
}
52-
export const Input = React.forwardRef<HTMLInputElement, InputProps>(function _Input({ size, variant, ...props }, ref) {
56+
export const Input = React.forwardRef<HTMLInputElement, InputProps>(function _Input(
57+
{ size, variant, leftElement, rightElement, ...props },
58+
ref,
59+
) {
5360
return (
54-
<input
55-
type="text"
56-
ref={ref}
57-
id={props.name}
58-
{...props}
59-
className={merge(inputStyles({ variant, size }), inputSizeStyles({ size }), props.className)}
60-
/>
61+
<div className="flex flex-row">
62+
{leftElement && (
63+
<div className={merge(inputSizeStyles({ size }), "center rounded-l-xs border bg-gray-50 px-2 dark:bg-gray-900")}>
64+
{leftElement}
65+
</div>
66+
)}
67+
<input
68+
type="text"
69+
ref={ref}
70+
id={props.name}
71+
{...props}
72+
className={merge(
73+
inputStyles({ variant, size, className: props.className }),
74+
inputSizeStyles({ size }),
75+
leftElement && "rounded-l-none",
76+
rightElement && "rounded-r-none",
77+
)}
78+
/>
79+
{rightElement && (
80+
<div className={merge(inputSizeStyles({ size }), "center rounded-r-xs border bg-gray-50 px-2 dark:bg-gray-900")}>
81+
{rightElement}
82+
</div>
83+
)}
84+
</div>
6185
)
6286
})
6387

@@ -66,48 +90,9 @@ export interface TextareaProps
6690
InputStyleProps {
6791
name?: string
6892
}
69-
const paddingMap = {
70-
xs: 4,
71-
sm: 6,
72-
md: 8,
73-
lg: 12,
74-
} as const
7593

76-
const lineHeightMap = {
77-
xs: 16,
78-
sm: 20,
79-
md: 24,
80-
lg: 28,
81-
}
8294
export function Textarea({ variant, size, ...props }: TextareaProps) {
83-
const ref = React.useRef<HTMLTextAreaElement>(null)
84-
// Dealing with Textarea Height
85-
const calcHeight = React.useCallback(
86-
(value: string) => {
87-
if (!ref.current) return
88-
const numberOfLineBreaks = (value.match(/\n/g) || []).length
89-
const lineHeight = lineHeightMap[size || "sm"]
90-
// min-height + lines x line-height + padding + border
91-
const newHeight = lineHeight + numberOfLineBreaks * lineHeight + paddingMap[size || "sm"] * 2 + 2
92-
ref.current.style.height = `${newHeight}px`
93-
},
94-
[size],
95-
)
96-
97-
React.useEffect(() => {
98-
if (!ref.current) return
99-
calcHeight(ref.current.value)
100-
}, [calcHeight])
101-
102-
return (
103-
<textarea
104-
ref={ref}
105-
id={props.name}
106-
{...props}
107-
onChange={(e) => calcHeight(e.currentTarget.value)}
108-
className={merge(inputStyles({ variant, size }), "resize-none", props.className)}
109-
/>
110-
)
95+
return <textarea id={props.name} {...props} className={merge(inputStyles({ variant, size }), "resize-none", props.className)} />
11196
}
11297

11398
export interface SelectProps
@@ -121,7 +106,7 @@ export function Select({ variant, size, ...props }: SelectProps) {
121106
<select
122107
id={props.name}
123108
{...props}
124-
className={merge(inputStyles({ variant, size }), inputSizeStyles({ size }), props.className)}
109+
className={merge(inputStyles({ variant, size }), inputSizeStyles({ size }), "w-auto pr-8", props.className)}
125110
>
126111
{props.children}
127112
</select>
@@ -131,30 +116,40 @@ export function Select({ variant, size, ...props }: SelectProps) {
131116
export const checkboxSizeStyles = cva("", {
132117
variants: {
133118
size: {
134-
sm: "sq-5",
135-
md: "sq-7",
136-
lg: "sq-9",
119+
sm: "sq-4",
120+
md: "sq-5",
121+
lg: "sq-7",
137122
},
138123
},
139124
defaultVariants: {
140-
size: "sm",
125+
size: "md",
141126
},
142127
})
143128
export type CheckboxSizeStyleProps = VariantProps<typeof checkboxSizeStyles>
144129

145130
export function Checkbox({
146-
size = "sm",
131+
size = "md",
132+
isInderterminate,
147133
...props
148134
}: Omit<React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "size"> &
149-
CheckboxSizeStyleProps) {
135+
CheckboxSizeStyleProps & { isInderterminate?: boolean }) {
136+
const ref = React.useRef<HTMLInputElement>(null)
137+
React.useEffect(() => {
138+
if (!ref.current) return
139+
if (isInderterminate) {
140+
ref.current.indeterminate = !props.checked && isInderterminate
141+
}
142+
}, [isInderterminate, props.checked])
143+
150144
return (
151145
<input
146+
ref={ref}
152147
type="checkbox"
153148
{...props}
154149
className={merge(
155-
inputStyles({ variant: "outline", size: "xs" }),
150+
inputStyles({ variant: "outline" }),
156151
checkboxSizeStyles({ size }),
157-
"text-primary-500 checked:bg-primary-500 hover:text-primary-600 focus:ring-primary-300 dark:checked:bg-primary-500 dark:hover:checked:bg-primary-600 dark:focus:ring-primary-300 cursor-pointer transition-all ",
152+
"text-primary-500 checked:bg-primary-500 hover:text-primary-600 focus:ring-primary-300 dark:checked:bg-primary-500 dark:hover:checked:bg-primary-600 dark:focus:ring-primary-300 flex-shrink-0 cursor-pointer p-0 transition-all",
158153
props.className,
159154
)}
160155
/>

0 commit comments

Comments
 (0)