1
- import { useRef , useState , forwardRef , type Ref } from 'react' ;
1
+ import { forwardRef , type Ref } from 'react' ;
2
2
import { cva , type VariantProps } from 'cva' ;
3
+ import { useProgressBar } from 'react-aria' ;
3
4
import {
4
5
Button as RACButton ,
5
6
Link as RACLink ,
6
7
type ButtonProps as RACButtonProps ,
7
8
} from 'react-aria-components' ;
8
9
import { LoadingSpinner } from '@obosbbl/grunnmuren-icons-react' ;
9
- import { mergeRefs , useLayoutEffect } from '@react-aria/utils ' ;
10
+ import { useLocale , type Locale } from '../use-locale ' ;
10
11
11
12
/**
12
13
* Figma: https://www.figma.com/file/9OvSg0ZXI5E1eQYi7AWiWn/Grunnmuren-2.0-%E2%94%82-Designsystem?node-id=30%3A2574&mode=dev
13
14
*/
14
15
15
16
const buttonVariants = cva ( {
16
17
base : [
17
- 'inline-flex min-h-[44px] cursor-pointer items-center justify-center whitespace-nowrap rounded-lg font-medium transition-colors duration-200 focus-visible:outline-focus-offset' ,
18
+ 'relative inline-flex min-h-[44px] cursor-pointer items-center justify-center whitespace-nowrap rounded-lg font-medium transition-colors duration-200 focus-visible:outline-focus-offset' ,
18
19
] ,
19
20
variants : {
20
21
/**
@@ -44,56 +45,69 @@ const buttonVariants = cva({
44
45
true : 'p-2 [&>svg]:h-7 [&>svg]:w-7' ,
45
46
false : 'gap-2.5 px-4 py-2' ,
46
47
} ,
48
+ // Make the content of the button transparent to hide it's content, but keep the button width
49
+ isPending : { true : '!text-transparent' , false : null } ,
47
50
} ,
48
51
compoundVariants : [
49
52
{
50
53
color : 'green' ,
51
54
variant : 'primary' ,
52
55
// Darken bg by 20% on hover. The color is manually crafted
53
- className : 'bg-green text-white hover:bg-green-dark active:bg-[#007352]' ,
56
+ className :
57
+ 'bg-green text-white hover:bg-green-dark active:bg-[#007352] [&_[role="progressbar"]]:text-white' ,
54
58
} ,
55
59
{
56
60
color : 'green' ,
57
61
variant : 'secondary' ,
58
62
className :
59
- 'text-black shadow-green hover:bg-green hover:text-white active:bg-green' ,
63
+ 'text-black shadow-green hover:bg-green hover:text-white active:bg-green [&:hover_[role="progressbar"]]:text-white [&_[role="progressbar"]]:text-black' ,
64
+ } ,
65
+ {
66
+ color : 'green' ,
67
+ variant : 'tertiary' ,
68
+ className : '[&_[role="progressbar"]]:text-black' ,
60
69
} ,
61
70
{
62
71
color : 'mint' ,
63
72
variant : 'primary' ,
64
73
// Darken bg by 20% on hover. The color is manually crafted
65
- className : 'active:[#9ddac6] bg-mint text-black hover:bg-[#8dd4bd]' ,
74
+ className :
75
+ 'active:[#9ddac6] bg-mint text-black hover:bg-[#8dd4bd] [&_[role="progressbar"]]:text-black' ,
66
76
} ,
67
77
{
68
78
color : 'mint' ,
69
79
variant : 'secondary' ,
70
- className : 'text-mint shadow-mint hover:bg-mint hover:text-black' ,
80
+ className :
81
+ 'text-mint shadow-mint hover:bg-mint hover:text-black [&:hover_[role="progressbar"]]:text-black [&_[role="progressbar"]]:text-mint' ,
71
82
} ,
72
83
{
73
84
color : 'mint' ,
74
85
variant : 'tertiary' ,
75
- className : 'text-mint' ,
86
+ className : 'text-mint [&_[role="progressbar"]]:text-mint ' ,
76
87
} ,
77
88
{
78
89
color : 'white' ,
79
90
variant : 'primary' ,
80
- className : 'bg-white text-black hover:bg-sky active:bg-sky-light' ,
91
+ className :
92
+ 'bg-white text-black hover:bg-sky active:bg-sky-light [&_[role="progressbar"]]:text-black' ,
81
93
} ,
82
94
{
83
95
color : 'white' ,
84
96
variant : 'secondary' ,
85
- className : 'text-white shadow-white hover:bg-white hover:text-black' ,
97
+ className :
98
+ 'text-white shadow-white hover:bg-white hover:text-black [&:hover_[role="progressbar"]]:text-black [&_[role="progressbar"]]:text-white' ,
86
99
} ,
87
100
{
88
101
color : 'white' ,
89
102
variant : 'tertiary' ,
90
- className : 'text-white' ,
103
+ className : 'text-white [&_[role="progressbar"]]:text-white ' ,
91
104
} ,
92
105
] ,
93
106
defaultVariants : {
94
107
variant : 'primary' ,
95
108
color : 'green' ,
96
109
isIconOnly : false ,
110
+ isPending : false ,
97
111
} ,
98
112
} ) ;
99
113
@@ -102,9 +116,12 @@ type ButtonOrLinkProps = VariantProps<typeof buttonVariants> & {
102
116
href ?: string ;
103
117
/**
104
118
* Display the button in a loading state
119
+ * @deprecated Use isPending instead.
105
120
* @default false
106
121
*/
107
122
isLoading ?: boolean ;
123
+ /** Additional style properties for the element. */
124
+ style ?: React . CSSProperties ;
108
125
} ;
109
126
110
127
type ButtonProps = (
@@ -119,58 +136,69 @@ function isLinkProps(
119
136
return ! ! props . href ;
120
137
}
121
138
139
+ type Translation = {
140
+ [ key in Locale ] : string ;
141
+ } ;
142
+
143
+ type Translations = {
144
+ [ x : string ] : Translation ;
145
+ } ;
146
+
147
+ const translations : Translations = {
148
+ pending : {
149
+ nb : 'venter' ,
150
+ sv : 'väntar' ,
151
+ en : 'pending' ,
152
+ } ,
153
+ } ;
154
+
122
155
function Button (
123
156
props : ButtonProps ,
124
- forwardedRef : Ref < HTMLButtonElement | HTMLAnchorElement > ,
157
+ ref : Ref < HTMLButtonElement | HTMLAnchorElement > ,
125
158
) {
126
159
const {
127
160
children : _children ,
128
161
color,
129
162
isIconOnly,
130
163
isLoading,
131
164
variant,
132
- style : _style ,
165
+ isPending : _isPending ,
133
166
...restProps
134
167
} = props ;
135
168
136
- const [ widthOverride , setWidthOverride ] = useState < number > ( ) ;
137
-
138
- const ownRef = useRef < HTMLButtonElement | HTMLAnchorElement > ( null ) ;
139
- const ref = mergeRefs ( ownRef , forwardedRef ) ;
140
-
141
- useLayoutEffect ( ( ) => {
142
- if ( isLoading ) {
143
- const requestID = window . requestAnimationFrame ( ( ) => {
144
- setWidthOverride ( ownRef . current ?. getBoundingClientRect ( ) ?. width ) ;
145
- } ) ;
146
- return ( ) => {
147
- setWidthOverride ( undefined ) ;
148
- cancelAnimationFrame ( requestID ) ;
149
- } ;
150
- }
151
- } , [ isLoading , _children ] ) ;
169
+ const isPending = _isPending || isLoading ;
152
170
153
171
const className = buttonVariants ( {
154
172
className : props . className ,
155
173
color,
156
174
isIconOnly,
157
175
variant,
176
+ isPending,
158
177
} ) ;
159
178
160
- const children = widthOverride ? (
161
- // remove margin for icon alignment
162
- < LoadingSpinner className = "!m-0 mx-auto animate-spin" />
179
+ const locale = useLocale ( ) ;
180
+
181
+ const { progressBarProps } = useProgressBar ( {
182
+ isIndeterminate : true ,
183
+ 'aria-label' : translations . pending [ locale ] ,
184
+ } ) ;
185
+
186
+ const children = isPending ? (
187
+ < >
188
+ { _children }
189
+ < LoadingSpinner
190
+ className = "absolute m-auto motion-safe:animate-spin"
191
+ { ...progressBarProps }
192
+ />
193
+ </ >
163
194
) : (
164
195
_children
165
196
) ;
166
197
167
- const style = { ..._style , width : widthOverride } ;
168
-
169
198
return isLinkProps ( restProps ) ? (
170
199
< RACLink
171
200
{ ...restProps }
172
201
className = { className }
173
- style = { style }
174
202
ref = { ref as React . ForwardedRef < HTMLAnchorElement > }
175
203
>
176
204
{ children }
@@ -179,7 +207,7 @@ function Button(
179
207
< RACButton
180
208
{ ...restProps }
181
209
className = { className }
182
- style = { style }
210
+ isPending = { isPending }
183
211
ref = { ref as React . ForwardedRef < HTMLButtonElement > }
184
212
>
185
213
{ children }
0 commit comments