@ciromaciel/auth-react 1.3.0 → 1.4.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 +26 -1
- package/dist/index.esm.js +327 -120
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +325 -118
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -139,6 +139,31 @@ Building your own screen? `fetchRecentAccounts()`, `saveRecentAccount(method)` a
|
|
|
139
139
|
`rememberAccount(email, method)`, `forgetAccount(email)` and `adoptRecentAccounts(list)` manage
|
|
140
140
|
the local copy.
|
|
141
141
|
|
|
142
|
+
### The account card
|
|
143
|
+
|
|
144
|
+
`<UserInformation />` is what opens from the footer of a sidebar: who is signed in, in which
|
|
145
|
+
organization and on which plan, then one list of actions with "Sair" last.
|
|
146
|
+
|
|
147
|
+
```jsx
|
|
148
|
+
<UserInformation
|
|
149
|
+
user={user}
|
|
150
|
+
signOut={signOut}
|
|
151
|
+
onAccountClick={openAccount}
|
|
152
|
+
onBillingClick={openBilling}
|
|
153
|
+
items={[{ id: 'docs', label: 'Documentação', icon: IconFileText, onClick: openDocs }]}
|
|
154
|
+
plan={{ name: 'Pro', used: 3, limit: 9, unit: 'projetos', onClick: openPlans }}
|
|
155
|
+
organization={{ name: 'Acme', role: 'owner' }}
|
|
156
|
+
/>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
- The title is the user's name, or the email when there is none — never both saying the same
|
|
160
|
+
thing.
|
|
161
|
+
- `items` are your app's own rows, passed as data (`{ id, label, icon, onClick }`), shown in their
|
|
162
|
+
own group after billing. The component takes no JSX, so every app's card keeps the same shape.
|
|
163
|
+
- `plan` and `organization` draw the context strip; leave them out and it is not drawn.
|
|
164
|
+
`limit: null` hides the usage bar.
|
|
165
|
+
- `branded={false}` removes the "Protegido por Auth" line.
|
|
166
|
+
|
|
142
167
|
## API
|
|
143
168
|
|
|
144
169
|
### Components
|
|
@@ -147,7 +172,7 @@ the local copy.
|
|
|
147
172
|
| ------------------------- | ------------------------------------------------------------ |
|
|
148
173
|
| `<SignIn />` | The whole way in: asks for the email, then takes the code |
|
|
149
174
|
| `<UserProfile />` | User profile management modal |
|
|
150
|
-
| `<UserInformation />` |
|
|
175
|
+
| `<UserInformation />` | The account card: who, where, which plan, then the actions |
|
|
151
176
|
| `<SocialButtons />` | Sign-in buttons for the providers the application enabled |
|
|
152
177
|
| `<AuthCard />` | The card shell the screens are built on |
|
|
153
178
|
| `<Wordmark />` | Application wordmark |
|
package/dist/index.esm.js
CHANGED
|
@@ -3,9 +3,9 @@ import { useState, useCallback, useEffect, useMemo, createContext, useRef } from
|
|
|
3
3
|
import { useShallow } from 'zustand/react/shallow';
|
|
4
4
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
5
|
import { Navigate, Outlet, useNavigate } from 'react-router-dom';
|
|
6
|
-
import { Modal, Stack, Text, Group, Image, Title, Paper, Anchor, NavLink, ActionIcon, Loader, Avatar, Button, Divider, TextInput, Alert, Center, Box, Collapse, FileButton, Tooltip, ThemeIcon, Badge,
|
|
6
|
+
import { Modal, Stack, Text, Group, Image, Title, Paper, Anchor, NavLink, ActionIcon, Loader, Avatar, Button, Divider, TextInput, Alert, Center, Box, Collapse, FileButton, Tooltip, ThemeIcon, Badge, UnstyledButton } from '@mantine/core';
|
|
7
7
|
import { useForm } from '@mantine/form';
|
|
8
|
-
import { IconX, IconArrowRight, IconBrandGoogle, IconArrowLeft, IconRefresh, IconAlertCircle, IconUser, IconPhoto, IconTrash, IconCheck, IconPencil, IconMail, IconShield, IconDevices, IconDeviceMobile, IconLogout, IconUserCircle,
|
|
8
|
+
import { IconX, IconArrowRight, IconBrandGoogle, IconArrowLeft, IconRefresh, IconAlertCircle, IconUser, IconPhoto, IconTrash, IconCheck, IconPencil, IconMail, IconShield, IconDevices, IconDeviceMobile, IconLogout, IconUserCircle, IconBuilding, IconCreditCard } from '@tabler/icons-react';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* The accounts that already signed in on this browser.
|
|
@@ -3982,151 +3982,358 @@ function UserProfile({
|
|
|
3982
3982
|
});
|
|
3983
3983
|
}
|
|
3984
3984
|
|
|
3985
|
+
const ROLE_LABELS = {
|
|
3986
|
+
owner: 'Dono',
|
|
3987
|
+
admin: 'Administrador',
|
|
3988
|
+
member: 'Membro'
|
|
3989
|
+
};
|
|
3990
|
+
|
|
3991
|
+
/**
|
|
3992
|
+
* @typedef {Object} UserInformationItem
|
|
3993
|
+
* @property {string} [id] - Stable key
|
|
3994
|
+
* @property {string} label - The row's text
|
|
3995
|
+
* @property {Function} [icon] - A Tabler icon component
|
|
3996
|
+
* @property {Function} onClick
|
|
3997
|
+
*
|
|
3998
|
+
* @typedef {Object} UserInformationPlan
|
|
3999
|
+
* @property {string} [name] - "Pro", "Starter"… Shown as the badge
|
|
4000
|
+
* @property {number} [used] - How many of the plan's resources are in use
|
|
4001
|
+
* @property {number|null} [limit] - The plan's ceiling; `null` hides the meter
|
|
4002
|
+
* @property {string} [unit] - What is counted, in the plural: "projetos"
|
|
4003
|
+
* @property {string} [actionLabel='Ver planos']
|
|
4004
|
+
* @property {Function} [onClick] - Opens the plans
|
|
4005
|
+
*
|
|
4006
|
+
* @typedef {Object} UserInformationOrganization
|
|
4007
|
+
* @property {string} name
|
|
4008
|
+
* @property {string} [role] - `owner`, `admin`, `member`, or already a label
|
|
4009
|
+
*/
|
|
4010
|
+
|
|
4011
|
+
/**
|
|
4012
|
+
* @param {Object} props
|
|
4013
|
+
* @param {Object} props.user - The signed-in user (`name`, `email`, `image`)
|
|
4014
|
+
* @param {Function} props.signOut
|
|
4015
|
+
* @param {Function} [props.onAccountClick]
|
|
4016
|
+
* @param {Function} [props.onBillingClick]
|
|
4017
|
+
* @param {UserInformationItem[]} [props.items] - The panel's own rows, shown in their own group
|
|
4018
|
+
* @param {UserInformationPlan} [props.plan] - The plan strip; omitted, the strip is not drawn
|
|
4019
|
+
* @param {UserInformationOrganization} [props.organization] - Where the person is acting
|
|
4020
|
+
* @param {boolean} [props.branded=true] - The "Protegido por Auth" line
|
|
4021
|
+
* @param {string} [props.accountLabel='Conta']
|
|
4022
|
+
* @param {string} [props.billingLabel='Assinatura']
|
|
4023
|
+
* @param {string} [props.signOutLabel='Sair']
|
|
4024
|
+
*/
|
|
3985
4025
|
function UserInformation({
|
|
3986
4026
|
user,
|
|
3987
4027
|
signOut,
|
|
3988
4028
|
onAccountClick,
|
|
3989
4029
|
onBillingClick,
|
|
4030
|
+
items = [],
|
|
4031
|
+
plan,
|
|
4032
|
+
organization,
|
|
4033
|
+
branded = true,
|
|
3990
4034
|
accountLabel = 'Conta',
|
|
3991
4035
|
billingLabel = 'Assinatura',
|
|
4036
|
+
signOutLabel = 'Sair',
|
|
4037
|
+
// Kept so existing calls keep working. The card has one density now: the
|
|
4038
|
+
// popover's. `padded={false}` still removes the outer padding.
|
|
3992
4039
|
padded = true,
|
|
3993
|
-
size
|
|
4040
|
+
size,
|
|
4041
|
+
// eslint-disable-line no-unused-vars -- accepted and ignored, see above
|
|
3994
4042
|
style,
|
|
3995
4043
|
...others
|
|
3996
4044
|
}) {
|
|
3997
4045
|
if (!user) return null;
|
|
4046
|
+
const name = (user.fullName || user.name || '').trim();
|
|
4047
|
+
const email = (user.primaryEmailAddress || user.email || '').trim();
|
|
3998
4048
|
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
const
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
md: 'sm',
|
|
4023
|
-
lg: 'md'
|
|
4024
|
-
};
|
|
4025
|
-
const widthMap = {
|
|
4026
|
-
sm: 280,
|
|
4027
|
-
md: 320,
|
|
4028
|
-
lg: 400
|
|
4029
|
-
};
|
|
4030
|
-
const name = user.fullName || user.name || 'User';
|
|
4031
|
-
const email = user.primaryEmailAddress || user.email || '';
|
|
4032
|
-
const initials = name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
|
|
4033
|
-
return /*#__PURE__*/jsx(Box, {
|
|
4034
|
-
p: padded ? gapMap[size] : 0,
|
|
4035
|
-
w: widthMap[size],
|
|
4049
|
+
/*
|
|
4050
|
+
* A name equal to the email's local part is not a name: it is what an
|
|
4051
|
+
* account born from a code gets by default, and showing it on top of the
|
|
4052
|
+
* email repeats the same fact. It counts as "no name".
|
|
4053
|
+
*/
|
|
4054
|
+
const hasRealName = Boolean(name) && name.toLowerCase() !== email.split('@')[0].toLowerCase() && name.toLowerCase() !== email.toLowerCase();
|
|
4055
|
+
const title = hasRealName ? name : email || name;
|
|
4056
|
+
const initials = hasRealName ? name.split(/\s+/).map(part => part[0]).join('').slice(0, 2).toUpperCase() : (email || name).charAt(0).toUpperCase();
|
|
4057
|
+
const baseRows = [onAccountClick && {
|
|
4058
|
+
id: 'account',
|
|
4059
|
+
label: accountLabel,
|
|
4060
|
+
icon: IconUser,
|
|
4061
|
+
onClick: onAccountClick
|
|
4062
|
+
}, onBillingClick && {
|
|
4063
|
+
id: 'billing',
|
|
4064
|
+
label: billingLabel,
|
|
4065
|
+
icon: IconCreditCard,
|
|
4066
|
+
onClick: onBillingClick
|
|
4067
|
+
}].filter(Boolean);
|
|
4068
|
+
const panelRows = items.filter(item => item && item.label && typeof item.onClick === 'function');
|
|
4069
|
+
return /*#__PURE__*/jsxs(Box, {
|
|
4070
|
+
w: 288,
|
|
4071
|
+
maw: "100%",
|
|
4036
4072
|
style: style,
|
|
4037
4073
|
...others,
|
|
4038
|
-
children: /*#__PURE__*/jsxs(
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
}
|
|
4074
|
+
children: [/*#__PURE__*/jsxs(Group, {
|
|
4075
|
+
wrap: "nowrap",
|
|
4076
|
+
gap: 10,
|
|
4077
|
+
px: padded ? 16 : 0,
|
|
4078
|
+
py: 14,
|
|
4079
|
+
children: [/*#__PURE__*/jsx(Avatar, {
|
|
4080
|
+
src: user.imageUrl || user.image,
|
|
4081
|
+
alt: "",
|
|
4082
|
+
size: 32,
|
|
4083
|
+
radius: 0,
|
|
4084
|
+
color: "gray.9",
|
|
4085
|
+
variant: "filled",
|
|
4086
|
+
styles: {
|
|
4087
|
+
root: {
|
|
4088
|
+
borderRadius: 0
|
|
4054
4089
|
},
|
|
4055
|
-
|
|
4056
|
-
|
|
4090
|
+
placeholder: {
|
|
4091
|
+
fontSize: 12,
|
|
4092
|
+
fontWeight: 800
|
|
4093
|
+
}
|
|
4094
|
+
},
|
|
4095
|
+
children: initials
|
|
4096
|
+
}), /*#__PURE__*/jsxs(Box, {
|
|
4097
|
+
style: {
|
|
4098
|
+
flex: 1,
|
|
4099
|
+
minWidth: 0
|
|
4100
|
+
},
|
|
4101
|
+
children: [/*#__PURE__*/jsx(Text, {
|
|
4102
|
+
fz: 13,
|
|
4103
|
+
fw: 800,
|
|
4104
|
+
c: "gray.9",
|
|
4105
|
+
lh: 1.3,
|
|
4106
|
+
truncate: "end",
|
|
4107
|
+
children: title
|
|
4108
|
+
}), hasRealName && email && /*#__PURE__*/jsx(Text, {
|
|
4109
|
+
fz: 12,
|
|
4110
|
+
fw: 500,
|
|
4111
|
+
c: "gray.5",
|
|
4112
|
+
lh: 1.4,
|
|
4113
|
+
truncate: "end",
|
|
4114
|
+
children: email
|
|
4115
|
+
})]
|
|
4116
|
+
})]
|
|
4117
|
+
}), (organization?.name || plan) && /*#__PURE__*/jsx(ContextStrip, {
|
|
4118
|
+
organization: organization,
|
|
4119
|
+
plan: plan,
|
|
4120
|
+
padded: padded
|
|
4121
|
+
}), /*#__PURE__*/jsxs(Box, {
|
|
4122
|
+
role: "menu",
|
|
4123
|
+
"aria-label": "Conta",
|
|
4124
|
+
onKeyDown: moveFocus,
|
|
4125
|
+
children: [baseRows.length > 0 && /*#__PURE__*/jsx(RowGroup, {
|
|
4126
|
+
rows: baseRows,
|
|
4127
|
+
hasDivider: !organization?.name && !plan
|
|
4128
|
+
}), panelRows.length > 0 && /*#__PURE__*/jsx(RowGroup, {
|
|
4129
|
+
rows: panelRows,
|
|
4130
|
+
hasDivider: true
|
|
4131
|
+
}), signOut && /*#__PURE__*/jsx(RowGroup, {
|
|
4132
|
+
rows: [{
|
|
4133
|
+
id: 'sign-out',
|
|
4134
|
+
label: signOutLabel,
|
|
4135
|
+
icon: IconLogout,
|
|
4136
|
+
onClick: signOut
|
|
4137
|
+
}],
|
|
4138
|
+
hasDivider: true
|
|
4139
|
+
})]
|
|
4140
|
+
}), branded && /*#__PURE__*/jsxs(Group, {
|
|
4141
|
+
justify: "center",
|
|
4142
|
+
gap: 4,
|
|
4143
|
+
py: 8,
|
|
4144
|
+
bg: "gray.1",
|
|
4145
|
+
style: {
|
|
4146
|
+
borderTop: '1px solid var(--mantine-color-gray-2)'
|
|
4147
|
+
},
|
|
4148
|
+
children: [/*#__PURE__*/jsx(Text, {
|
|
4149
|
+
fz: 11,
|
|
4150
|
+
fw: 500,
|
|
4151
|
+
c: "gray.5",
|
|
4152
|
+
children: "Protegido por"
|
|
4153
|
+
}), /*#__PURE__*/jsx(Text, {
|
|
4154
|
+
fz: 11,
|
|
4155
|
+
fw: 800,
|
|
4156
|
+
c: "gray.7",
|
|
4157
|
+
children: "Auth"
|
|
4158
|
+
})]
|
|
4159
|
+
})]
|
|
4160
|
+
});
|
|
4161
|
+
}
|
|
4162
|
+
|
|
4163
|
+
/** The organization, the role and the plan's usage, on the alternate surface. */
|
|
4164
|
+
function ContextStrip({
|
|
4165
|
+
organization,
|
|
4166
|
+
plan,
|
|
4167
|
+
padded
|
|
4168
|
+
}) {
|
|
4169
|
+
const role = organization?.role ? ROLE_LABELS[organization.role] || organization.role : null;
|
|
4170
|
+
const hasMeter = plan && Number.isFinite(plan.used) && Number.isFinite(plan.limit) && plan.limit > 0;
|
|
4171
|
+
const ratio = hasMeter ? Math.min(1, Math.max(0, plan.used / plan.limit)) : 0;
|
|
4172
|
+
return /*#__PURE__*/jsxs(Stack, {
|
|
4173
|
+
gap: 8,
|
|
4174
|
+
px: padded ? 16 : 0,
|
|
4175
|
+
py: 12,
|
|
4176
|
+
bg: "gray.1",
|
|
4177
|
+
style: {
|
|
4178
|
+
borderTop: '1px solid var(--mantine-color-gray-2)',
|
|
4179
|
+
borderBottom: '1px solid var(--mantine-color-gray-2)'
|
|
4180
|
+
},
|
|
4181
|
+
children: [(organization?.name || plan?.name) && /*#__PURE__*/jsxs(Group, {
|
|
4182
|
+
gap: 8,
|
|
4183
|
+
wrap: "nowrap",
|
|
4184
|
+
children: [organization?.name && /*#__PURE__*/jsxs(Fragment, {
|
|
4185
|
+
children: [/*#__PURE__*/jsx(IconBuilding, {
|
|
4186
|
+
size: 14,
|
|
4187
|
+
stroke: 1.5,
|
|
4057
4188
|
style: {
|
|
4058
|
-
flex:
|
|
4059
|
-
|
|
4189
|
+
flex: 'none',
|
|
4190
|
+
color: 'var(--mantine-color-gray-5)'
|
|
4191
|
+
}
|
|
4192
|
+
}), /*#__PURE__*/jsx(Text, {
|
|
4193
|
+
fz: 12,
|
|
4194
|
+
fw: 800,
|
|
4195
|
+
c: "gray.9",
|
|
4196
|
+
truncate: "end",
|
|
4197
|
+
style: {
|
|
4198
|
+
minWidth: 0
|
|
4060
4199
|
},
|
|
4061
|
-
children:
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
}
|
|
4069
|
-
|
|
4070
|
-
c: "gray.5",
|
|
4071
|
-
truncate: "end",
|
|
4072
|
-
lh: 1.1,
|
|
4073
|
-
children: email
|
|
4074
|
-
})]
|
|
4075
|
-
}), /*#__PURE__*/jsx(ActionIcon, {
|
|
4076
|
-
size: btnSizeMap[size],
|
|
4077
|
-
onClick: signOut,
|
|
4078
|
-
children: /*#__PURE__*/jsx(IconLogout, {
|
|
4079
|
-
size: 16,
|
|
4080
|
-
stroke: 1.5
|
|
4081
|
-
})
|
|
4082
|
-
})]
|
|
4083
|
-
}), /*#__PURE__*/jsxs(Group, {
|
|
4084
|
-
grow: true,
|
|
4085
|
-
children: [/*#__PURE__*/jsx(Button, {
|
|
4086
|
-
variant: "default",
|
|
4087
|
-
size: btnSizeMap[size],
|
|
4088
|
-
leftSection: /*#__PURE__*/jsx(IconSettings, {
|
|
4089
|
-
size: 16,
|
|
4090
|
-
stroke: 1.5
|
|
4091
|
-
}),
|
|
4092
|
-
onClick: onAccountClick,
|
|
4093
|
-
children: accountLabel
|
|
4094
|
-
}), /*#__PURE__*/jsx(Button, {
|
|
4095
|
-
variant: "default",
|
|
4096
|
-
size: btnSizeMap[size],
|
|
4097
|
-
leftSection: /*#__PURE__*/jsx(IconCreditCard, {
|
|
4098
|
-
size: 16,
|
|
4099
|
-
stroke: 1.5
|
|
4100
|
-
}),
|
|
4101
|
-
onClick: onBillingClick,
|
|
4102
|
-
children: billingLabel
|
|
4200
|
+
children: organization.name
|
|
4201
|
+
}), role && /*#__PURE__*/jsxs(Text, {
|
|
4202
|
+
fz: 12,
|
|
4203
|
+
fw: 500,
|
|
4204
|
+
c: "gray.5",
|
|
4205
|
+
style: {
|
|
4206
|
+
flex: 'none'
|
|
4207
|
+
},
|
|
4208
|
+
children: ["\xB7 ", role]
|
|
4103
4209
|
})]
|
|
4210
|
+
}), plan?.name && /*#__PURE__*/jsx(Text, {
|
|
4211
|
+
component: "span",
|
|
4212
|
+
fz: 10,
|
|
4213
|
+
fw: 800,
|
|
4214
|
+
tt: "uppercase",
|
|
4215
|
+
lts: "1.5px",
|
|
4216
|
+
c: "white",
|
|
4217
|
+
bg: "gray.9",
|
|
4218
|
+
px: 6,
|
|
4219
|
+
lh: 1.6,
|
|
4220
|
+
ml: "auto",
|
|
4221
|
+
style: {
|
|
4222
|
+
flex: 'none'
|
|
4223
|
+
},
|
|
4224
|
+
children: plan.name
|
|
4225
|
+
})]
|
|
4226
|
+
}), hasMeter && /*#__PURE__*/jsxs(Fragment, {
|
|
4227
|
+
children: [/*#__PURE__*/jsx(Box, {
|
|
4228
|
+
h: 4,
|
|
4229
|
+
bg: "gray.2",
|
|
4230
|
+
role: "meter",
|
|
4231
|
+
"aria-valuemin": 0,
|
|
4232
|
+
"aria-valuemax": plan.limit,
|
|
4233
|
+
"aria-valuenow": plan.used,
|
|
4234
|
+
"aria-label": plan.unit ? `${plan.unit} em uso` : 'Uso do plano',
|
|
4235
|
+
children: /*#__PURE__*/jsx(Box, {
|
|
4236
|
+
h: "100%",
|
|
4237
|
+
w: `${ratio * 100}%`,
|
|
4238
|
+
bg: "gray.9"
|
|
4239
|
+
})
|
|
4104
4240
|
}), /*#__PURE__*/jsxs(Group, {
|
|
4105
|
-
justify: "
|
|
4106
|
-
gap:
|
|
4107
|
-
|
|
4241
|
+
justify: "space-between",
|
|
4242
|
+
gap: 8,
|
|
4243
|
+
wrap: "nowrap",
|
|
4108
4244
|
children: [/*#__PURE__*/jsx(Text, {
|
|
4109
|
-
|
|
4245
|
+
fz: 12,
|
|
4246
|
+
fw: 500,
|
|
4110
4247
|
c: "gray.6",
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
c: "dark.9",
|
|
4122
|
-
children: "Auth"
|
|
4123
|
-
})]
|
|
4248
|
+
children: `${plan.used} de ${plan.limit}${plan.unit ? ` ${plan.unit}` : ''}`
|
|
4249
|
+
}), plan.onClick && /*#__PURE__*/jsx(Anchor, {
|
|
4250
|
+
component: "button",
|
|
4251
|
+
type: "button",
|
|
4252
|
+
fz: 12,
|
|
4253
|
+
fw: 700,
|
|
4254
|
+
c: "gray.9",
|
|
4255
|
+
underline: "always",
|
|
4256
|
+
onClick: plan.onClick,
|
|
4257
|
+
children: plan.actionLabel || 'Ver planos'
|
|
4124
4258
|
})]
|
|
4125
4259
|
})]
|
|
4126
|
-
})
|
|
4260
|
+
})]
|
|
4261
|
+
});
|
|
4262
|
+
}
|
|
4263
|
+
function RowGroup({
|
|
4264
|
+
rows,
|
|
4265
|
+
hasDivider
|
|
4266
|
+
}) {
|
|
4267
|
+
return /*#__PURE__*/jsx(Stack, {
|
|
4268
|
+
gap: 0,
|
|
4269
|
+
p: 6,
|
|
4270
|
+
style: hasDivider ? {
|
|
4271
|
+
borderTop: '1px solid var(--mantine-color-gray-2)'
|
|
4272
|
+
} : undefined,
|
|
4273
|
+
children: rows.map(row => /*#__PURE__*/jsx(Row, {
|
|
4274
|
+
...row
|
|
4275
|
+
}, row.id || row.label))
|
|
4276
|
+
});
|
|
4277
|
+
}
|
|
4278
|
+
function Row({
|
|
4279
|
+
label,
|
|
4280
|
+
icon: Icon,
|
|
4281
|
+
onClick
|
|
4282
|
+
}) {
|
|
4283
|
+
return /*#__PURE__*/jsxs(UnstyledButton, {
|
|
4284
|
+
role: "menuitem",
|
|
4285
|
+
onClick: onClick,
|
|
4286
|
+
px: 10,
|
|
4287
|
+
py: 7,
|
|
4288
|
+
w: "100%",
|
|
4289
|
+
style: {
|
|
4290
|
+
display: 'flex',
|
|
4291
|
+
alignItems: 'center',
|
|
4292
|
+
gap: 10,
|
|
4293
|
+
borderRadius: 0
|
|
4294
|
+
}
|
|
4295
|
+
/*
|
|
4296
|
+
* Hover and keyboard focus share the same surface: a row reached
|
|
4297
|
+
* with the arrows must look exactly like the one under the mouse.
|
|
4298
|
+
*/,
|
|
4299
|
+
onMouseEnter: event => event.currentTarget.style.background = 'var(--mantine-color-gray-1)',
|
|
4300
|
+
onMouseLeave: event => event.currentTarget.style.background = 'transparent',
|
|
4301
|
+
onFocus: event => event.currentTarget.style.background = 'var(--mantine-color-gray-1)',
|
|
4302
|
+
onBlur: event => event.currentTarget.style.background = 'transparent',
|
|
4303
|
+
children: [Icon && /*#__PURE__*/jsx(Icon, {
|
|
4304
|
+
size: 16,
|
|
4305
|
+
stroke: 1.5,
|
|
4306
|
+
style: {
|
|
4307
|
+
flex: 'none',
|
|
4308
|
+
color: 'var(--mantine-color-gray-5)'
|
|
4309
|
+
}
|
|
4310
|
+
}), /*#__PURE__*/jsx(Text, {
|
|
4311
|
+
fz: 12,
|
|
4312
|
+
fw: 500,
|
|
4313
|
+
c: "gray.9",
|
|
4314
|
+
truncate: "end",
|
|
4315
|
+
children: label
|
|
4316
|
+
})]
|
|
4127
4317
|
});
|
|
4128
4318
|
}
|
|
4129
4319
|
|
|
4320
|
+
/*
|
|
4321
|
+
* Arrow keys walk the rows, Home and End jump to the ends — the WAI-ARIA menu
|
|
4322
|
+
* pattern. Tab still leaves the card, so it never traps focus inside a popover
|
|
4323
|
+
* the panel owns.
|
|
4324
|
+
*/
|
|
4325
|
+
function moveFocus(event) {
|
|
4326
|
+
const keys = ['ArrowDown', 'ArrowUp', 'Home', 'End'];
|
|
4327
|
+
if (!keys.includes(event.key)) return;
|
|
4328
|
+
const rows = Array.from(event.currentTarget.querySelectorAll('[role="menuitem"]'));
|
|
4329
|
+
if (rows.length === 0) return;
|
|
4330
|
+
event.preventDefault();
|
|
4331
|
+
const current = rows.indexOf(document.activeElement);
|
|
4332
|
+
const last = rows.length - 1;
|
|
4333
|
+
const next = event.key === 'Home' ? 0 : event.key === 'End' ? last : event.key === 'ArrowDown' ? current < last ? current + 1 : 0 : current > 0 ? current - 1 : last;
|
|
4334
|
+
rows[next].focus();
|
|
4335
|
+
}
|
|
4336
|
+
|
|
4130
4337
|
/**
|
|
4131
4338
|
* Renderiza children apenas quando o usuário está autenticado
|
|
4132
4339
|
* Equivalente ao <SignedIn> do Clerk
|