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