personal web client for Bluesky
typescript
solidjs
bluesky
atcute
1import type { JSX } from 'solid-js';
2
3import { useFieldset } from './fieldset';
4
5export interface ButtonProps {
6 title?: string;
7 href?: string;
8 disabled?: boolean;
9 type?: 'button' | 'submit' | 'reset';
10 role?: JSX.AriaAttributes['role'];
11 onClick?: (ev: MouseEvent) => void;
12
13 children: JSX.Element;
14
15 size?: 'sm' | 'md' | 'lg';
16 variant?: 'outline' | 'primary' | 'danger' | 'ghost';
17 class?: string;
18}
19
20const Button = (props: ButtonProps) => {
21 const fieldset = useFieldset();
22 const isDisabled = (): boolean => fieldset.disabled || !!props.disabled;
23
24 if ('href' in props) {
25 return (
26 <a
27 role={props.role}
28 href={!isDisabled() ? props.href : undefined}
29 title={props.title}
30 onClick={props.onClick}
31 class={buttonClassNames(isDisabled, props)}
32 >
33 {props.children}
34 </a>
35 );
36 }
37
38 return (
39 <button
40 role={props.role}
41 type={props.type || 'button'}
42 disabled={isDisabled()}
43 title={props.title}
44 onClick={props.onClick}
45 class={buttonClassNames(isDisabled, props)}
46 >
47 {props.children}
48 </button>
49 );
50};
51
52export default Button;
53
54const buttonClassNames = (
55 isDisabled: () => boolean,
56 { size = 'sm', variant = 'outline', class: className }: ButtonProps,
57): string => {
58 var cn = `flex select-none items-center justify-center rounded text-sm font-semibold leading-none`;
59
60 if (isDisabled()) {
61 cn += ` pointer-events-none`;
62 }
63
64 if (size === 'sm') {
65 cn += ` h-8 px-4`;
66 } else if (size === 'md') {
67 cn += ` h-9 px-4`;
68 } else if (size === 'lg') {
69 cn += ` h-10 px-4`;
70 }
71
72 if (variant === 'primary') {
73 cn += ` bg-accent text-accent-fg`;
74
75 if (!isDisabled()) {
76 cn += ` hover:bg-accent-hover active:bg-accent-active`;
77 } else {
78 cn += ` opacity-50`;
79 }
80 } else if (variant === 'outline') {
81 cn += ` border border-outline-lg text-contrast`;
82
83 if (!isDisabled()) {
84 cn += ` hover:bg-contrast-hinted/md active:bg-contrast-hinted/md-pressed`;
85 } else {
86 cn += ` opacity-50`;
87 }
88 } else if (variant === 'ghost') {
89 cn += ` text-accent`;
90
91 if (!isDisabled()) {
92 cn += ` hover:bg-accent/md active:bg-accent/md-pressed`;
93 } else {
94 cn += ` opacity-50`;
95 }
96 } else if (variant === 'danger') {
97 cn += ` text-white bg-p-red-600`;
98
99 if (!isDisabled()) {
100 cn += ` hover:bg-p-red-700 active:bg-p-red-800`;
101 } else {
102 cn += ` opacity-50`;
103 }
104 }
105
106 if (className) {
107 cn += ` ${className}`;
108 }
109
110 return cn;
111};