Skip to content

Commit cdec11d

Browse files
Merge pull request #137 from linked-planet/dev
Dev
2 parents ad55482 + 4b2a9d9 commit cdec11d

File tree

9 files changed

+1346
-1106
lines changed

9 files changed

+1346
-1106
lines changed

library/src/components/Button.tsx

Lines changed: 225 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import type React from "react"
22
import {
3-
type CSSProperties,
43
forwardRef,
54
type HTMLProps,
65
useMemo,
76
useRef,
7+
type CSSProperties,
88
} from "react"
9-
import { twJoin, twMerge } from "tailwind-merge"
9+
import { twMerge } from "tailwind-merge"
10+
import { cva, cx, type VariantProps } from "class-variance-authority"
1011
import { LoadingSpinner } from "./LoadingSpinner"
1112

1213
export type ButtonAppearance =
@@ -20,93 +21,211 @@ export type ButtonAppearance =
2021
| "success"
2122
| "information"
2223

24+
type ButtonVariantProps = VariantProps<typeof buttonVariants>
25+
const buttonVariants = cva(
26+
"focus-visible:outline-selected-bold relative box-border flex shrink-0 cursor-pointer items-center justify-center gap-1 rounded-sm border-2 border-transparent px-3 py-1 outline-none outline-2 outline-offset-4 focus-visible:outline-solid",
27+
{
28+
variants: {
29+
appearance: {
30+
// those entries are undefined, they just establish the variant for the compoundVariants
31+
default: undefined,
32+
primary: undefined,
33+
34+
subtle: undefined,
35+
link: undefined,
36+
"subtle-link": undefined,
37+
warning: undefined,
38+
39+
danger: undefined,
40+
41+
success: undefined,
42+
43+
information: undefined,
44+
},
45+
disabled: {
46+
true: "disabled:bg-disabled disabled:text-disabled-text disabled:cursor-not-allowed",
47+
},
48+
selected: {
49+
true: "bg-selected active:bg-selected hover:bg-selected text-selected-text-inverse cursor-pointer",
50+
},
51+
inverted: {
52+
true: undefined,
53+
},
54+
loading: {
55+
true: "cursor-progress",
56+
},
57+
},
58+
compoundVariants: [
59+
{
60+
inverted: true,
61+
disabled: true,
62+
className: "disabled:border-border disabled:bg-transparent",
63+
},
64+
{
65+
inverted: false,
66+
appearance: "default",
67+
disabled: false,
68+
className:
69+
"bg-neutral hover:bg-neutral-hovered active:bg-neutral-pressed text-text",
70+
},
71+
{
72+
inverted: false,
73+
appearance: "primary",
74+
disabled: false,
75+
className:
76+
"bg-brand-bold hover:bg-brand-bold-hovered active:bg-brand-bold-pressed text-text-inverse",
77+
},
78+
{
79+
inverted: false,
80+
appearance: "subtle",
81+
disabled: false,
82+
className:
83+
"bg-neutral-subtle hover:bg-neutral-subtle-hovered active:bg-neutral-subtle-pressed text-text",
84+
},
85+
{
86+
inverted: false,
87+
appearance: "link",
88+
disabled: false,
89+
className:
90+
"bg-transparent disabled:bg-transparent text-link hover:underline",
91+
},
92+
{
93+
inverted: false,
94+
appearance: "subtle-link",
95+
disabled: false,
96+
className:
97+
"bg-transparent text-text-subtlest hover:text-text-subtle hover:underline",
98+
},
99+
{
100+
inverted: false,
101+
appearance: "warning",
102+
disabled: false,
103+
className:
104+
"bg-warning-bold hover:bg-warning-bold-hovered active:bg-warning-bold-pressed text-text-inverse",
105+
},
106+
{
107+
inverted: false,
108+
appearance: "danger",
109+
disabled: false,
110+
className:
111+
"bg-danger-bold hover:bg-danger-bold-hovered active:bg-danger-bold-pressed text-text-inverse",
112+
},
113+
{
114+
inverted: false,
115+
appearance: "success",
116+
disabled: false,
117+
className:
118+
"bg-success-bold hover:bg-success-bold-hovered active:bg-success-bold-pressed text-text-inverse",
119+
},
120+
{
121+
inverted: false,
122+
appearance: "information",
123+
disabled: false,
124+
className:
125+
"bg-information-bold hover:bg-information-bold-hovered active:bg-information-bold-pressed text-text-inverse",
126+
},
127+
{
128+
inverted: true,
129+
appearance: "default",
130+
className:
131+
"bg-transparent border-neutral-bold border-solid hover:bg-neutral-hovered active:bg-neutral-pressed",
132+
},
133+
{
134+
inverted: true,
135+
appearance: "primary",
136+
className: cx(
137+
"bg-brand hover:bg-brand-hovered active:bg-brand-pressed",
138+
"border-brand-bold text-brand-text border-solid",
139+
),
140+
},
141+
{
142+
inverted: true,
143+
appearance: "warning",
144+
className: cx(
145+
"bg-warning hover:bg-warning-hovered active:bg-warning-pressed",
146+
"border-warning-bold text-warning-text border-solid",
147+
),
148+
},
149+
{
150+
inverted: true,
151+
appearance: "danger",
152+
className: cx(
153+
"bg-danger hover:bg-danger-hovered active:bg-danger-pressed",
154+
"border-danger-bold text-danger-text border-solid",
155+
),
156+
},
157+
{
158+
inverted: true,
159+
appearance: "success",
160+
className: cx(
161+
"bg-success hover:bg-success-hovered active:bg-success-pressed",
162+
"border-success-bold text-success-text border-solid",
163+
),
164+
},
165+
{
166+
inverted: true,
167+
appearance: "information",
168+
className: cx(
169+
"bg-information hover:bg-information-hovered active:bg-information-pressed",
170+
"border-information-bold text-information-text border-solid",
171+
),
172+
},
173+
],
174+
defaultVariants: {
175+
appearance: "default",
176+
disabled: false,
177+
selected: false,
178+
inverted: false,
179+
loading: false,
180+
},
181+
},
182+
)
183+
23184
export type ButtonProps = {
24185
appearance?: ButtonAppearance
25186
label?: string
26187
title?: string
27188
iconBefore?: React.ReactNode
28189
iconAfter?: React.ReactNode
29-
disabled?: boolean
30-
selected?: boolean
31190
autoFocus?: boolean
32191
children?: React.ReactNode
33192
style?: CSSProperties
34193
className?: string
35-
inverted?: boolean
36194
id?: string
37195
href?: string
38196
download?: string | true
39197
target?: "_blank" | "_self" | "_parent" | "_top"
40198
"aria-label"?: string
41199
testId?: string
42-
} & Pick<
43-
React.ButtonHTMLAttributes<HTMLButtonElement>,
44-
| "type"
45-
| "onClick"
46-
| "onDoubleClick"
47-
| "onMouseDown"
48-
| "onMouseUp"
49-
| "onMouseEnter"
50-
| "onMouseLeave"
51-
| "onMouseOver"
52-
| "onMouseOut"
53-
| "onFocus"
54-
| "onBlur"
55-
| "onKeyDown"
56-
| "onKeyPress"
57-
| "onKeyUp"
58-
| "onPointerDown"
59-
| "onTouchStart"
60-
| "onTouchEnd"
61-
| "onTouchMove"
62-
| "onTouchCancel"
63-
| "title"
64-
| "aria-label"
65-
| "tabIndex"
66-
| "aria-disabled"
67-
>
68-
69-
const ButtonStyles: { [style in ButtonAppearance]: string } = {
70-
primary: twJoin(
71-
"bg-brand-bold hover:bg-brand-bold-hovered active:bg-brand-bold-pressed text-text-inverse",
72-
"data-inverted:bg-brand data-inverted:hover:bg-brand-hovered data-inverted:active:bg-brand-pressed",
73-
"data-inverted:border-brand-bold data-inverted:text-brand-text data-inverted:border-solid",
74-
),
75-
76-
default: twJoin(
77-
"bg-neutral hover:bg-neutral-hovered active:bg-neutral-pressed text-text",
78-
"data-inverted:bg-transparent data-inverted:border-neutral-bold data-inverted:border-solid data-inverted:hover:bg-neutral-hovered data-inverted:active:bg-neutral-pressed",
79-
),
80-
subtle: "bg-neutral-subtle hover:bg-neutral-subtle-hovered active:bg-neutral-subtle-pressed text-text",
81-
link: "bg-transparent text-link hover:underline",
82-
"subtle-link":
83-
"bg-transparent text-text-subtlest hover:text-text-subtle hover:underline",
84-
warning: twJoin(
85-
"bg-warning-bold hover:bg-warning-bold-hovered active:bg-warning-bold-pressed text-text-inverse",
86-
"data-inverted:bg-warning data-inverted:hover:bg-warning-hovered data-inverted:active:bg-warning-pressed",
87-
"data-inverted:border-warning-bold data-inverted:text-warning-text data-inverted:border-solid",
88-
),
89-
danger: twJoin(
90-
"bg-danger-bold hover:bg-danger-bold-hovered active:bg-danger-bold-pressed text-text-inverse",
91-
"data-inverted:bg-danger data-inverted:hover:bg-danger-hovered data-inverted:active:bg-danger-pressed",
92-
"data-inverted:border-danger-bold data-inverted:text-danger-text data-inverted:border-solid",
93-
),
94-
success: twJoin(
95-
"bg-success-bold hover:bg-success-bold-hovered active:bg-success-bold-pressed text-text-inverse",
96-
"data-inverted:bg-success data-inverted:hover:bg-success-hovered data-inverted:active:bg-success-pressed",
97-
"data-inverted:border-success-bold data-inverted:text-success-text data-inverted:border-solid",
98-
),
99-
information: twJoin(
100-
"bg-information-bold hover:bg-information-bold-hovered active:bg-information-bold-pressed text-text-inverse",
101-
"data-inverted:bg-information data-inverted:hover:bg-information-hovered data-inverted:active:bg-information-pressed",
102-
"data-inverted:border-information-bold data-inverted:text-information-text data-inverted:border-solid",
103-
),
104-
} as const
105-
106-
export const ButtonSelectedStyles =
107-
"bg-selected active:bg-selected hover:bg-selected text-selected-text-inverse cursor-pointer" as const
200+
} & ButtonVariantProps &
201+
Pick<
202+
React.ButtonHTMLAttributes<HTMLButtonElement>,
203+
| "type"
204+
| "onClick"
205+
| "onDoubleClick"
206+
| "onMouseDown"
207+
| "onMouseUp"
208+
| "onMouseEnter"
209+
| "onMouseLeave"
210+
| "onMouseOver"
211+
| "onMouseOut"
212+
| "onFocus"
213+
| "onBlur"
214+
| "onKeyDown"
215+
| "onKeyPress"
216+
| "onKeyUp"
217+
| "onPointerDown"
218+
| "onTouchStart"
219+
| "onTouchEnd"
220+
| "onTouchMove"
221+
| "onTouchCancel"
222+
| "title"
223+
| "aria-label"
224+
| "tabIndex"
225+
| "aria-disabled"
226+
>
108227

109-
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
228+
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
110229
(
111230
{
112231
label = "",
@@ -167,17 +286,16 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
167286
data-inverted={inverted}
168287
id={id}
169288
className={twMerge(
170-
"focus-visible:outline-selected-bold relative box-border flex shrink-0 cursor-pointer items-center justify-center gap-1 rounded-sm border-2 border-transparent px-3 py-1 outline-none outline-2 outline-offset-4 focus-visible:outline-solid",
171-
!disabled ? ButtonStyles[appearance] : undefined,
172-
`${
173-
appearance !== "subtle" && appearance !== "link"
174-
? "disabled:bg-disabled"
175-
: ""
176-
} disabled:text-disabled-text data-inverted:disabled:border-border disabled:cursor-not-allowed data-inverted:disabled:bg-transparent`,
177-
selected ? ButtonSelectedStyles : undefined,
289+
buttonVariants({
290+
appearance,
291+
disabled,
292+
selected,
293+
inverted,
294+
loading: false,
295+
}),
178296
className,
179297
)}
180-
disabled={disabled}
298+
disabled={disabled ?? false}
181299
data-testid={testId}
182300
{...props}
183301
>
@@ -187,20 +305,28 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
187305
},
188306
)
189307

190-
Button.displayName = "LPButton"
191-
export { Button }
192-
193-
const loadingSpinnerClassNames: { [appearance in ButtonAppearance]: string } = {
194-
primary: "border-t-text-inverse border-2",
195-
default: "border-t-border-bold border-2",
196-
subtle: "border-t-border-bold border-2",
197-
link: "border-t-border-bold border-2",
198-
"subtle-link": "border-t-border-bold border-2",
199-
warning: "border-t-text-inverse border-2",
200-
danger: "border-t-text-inverse border-2",
201-
success: "border-t-text-inverse border-2",
202-
information: "border-t-text-inverse border-2",
203-
}
308+
const loadingSpinnerClassNames = cva(null, {
309+
variants: {
310+
appearance: {
311+
primary: "border-t-text-inverse border-2",
312+
default: "border-t-border-bold border-2",
313+
subtle: "border-t-border-bold border-2",
314+
link: "border-t-border-bold border-2",
315+
"subtle-link": "border-t-border-bold border-2",
316+
warning: "border-t-text-inverse border-2",
317+
danger: "border-t-text-inverse border-2",
318+
success: "border-t-text-inverse border-2",
319+
information: "border-t-text-inverse border-2",
320+
},
321+
loading: {
322+
false: "opacity-0",
323+
},
324+
},
325+
defaultVariants: {
326+
appearance: "default",
327+
loading: false,
328+
},
329+
})
204330

205331
export const LoadingButton = ({
206332
loading = false,
@@ -235,7 +361,10 @@ export const LoadingButton = ({
235361
>
236362
<LoadingSpinner
237363
className={twMerge(
238-
loadingSpinnerClassNames[props.appearance ?? "default"],
364+
loadingSpinnerClassNames({
365+
appearance: props.appearance ?? "default",
366+
loading,
367+
}),
239368
loadingSpinnerClassName,
240369
)}
241370
style={{

library/src/components/Checkbox.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ import {
99
useRef,
1010
useState,
1111
} from "react"
12-
import { twJoin, twMerge } from "tailwind-merge"
12+
import { twMerge } from "tailwind-merge"
1313
import { SlidingErrorMessage } from "./inputs/ErrorHelpWrapper"
1414
import { CheckIcon, MinusIcon } from "lucide-react"
15+
import { cx } from "class-variance-authority"
1516

1617
type AdditionalCheckboxPropsWithIndeterminate = {
1718
checked?: boolean
@@ -45,7 +46,7 @@ type CheckboxProps = Omit<
4546

4647
const checkBoxSize = "size-4 box-border" as const
4748

48-
const checkBoxStyles = twJoin(
49+
const checkBoxStyles = cx(
4950
"bg-input m-0 p-0 hover:bg-input-hovered focus:bg-input-active border-border-bold border-solid border-2 box-border flex flex-none items-center justify-center ease-linear transition duration-150 cursor-default",
5051
"rounded-sm focus:outline-offset-2 focus:outline-2 focus:outline-brand-bold",
5152
checkBoxSize,

0 commit comments

Comments
 (0)