@ttoss/components 2.15.0 → 2.16.1
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 +46 -0
- package/dist/index.d.mts +114 -1
- package/dist/index.mjs +327 -2
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -14,6 +14,52 @@ pnpm add @ttoss/components @ttoss/ui @emotion/react @ttoss/react-hooks
|
|
|
14
14
|
|
|
15
15
|
All components are theme-aware and integrate seamlessly with `@ttoss/ui`.
|
|
16
16
|
|
|
17
|
+
### OAuthConsent
|
|
18
|
+
|
|
19
|
+
OAuth consent screen with flat and GitHub-style hierarchical scopes. Granting a parent scope automatically locks all descendants. All visible copy is injected via the `labels` prop — no hardcoded strings. [Docs](https://storybook.ttoss.dev/?path=/docs/components-oauthconsent--docs)
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { OAuthConsent } from '@ttoss/components';
|
|
23
|
+
|
|
24
|
+
<OAuthConsent
|
|
25
|
+
clientName="My App"
|
|
26
|
+
scopes={[
|
|
27
|
+
{
|
|
28
|
+
key: 'openid',
|
|
29
|
+
label: 'OpenID Connect',
|
|
30
|
+
description: 'Verify your identity',
|
|
31
|
+
required: true,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
key: 'profile',
|
|
35
|
+
label: 'Profile',
|
|
36
|
+
description: 'Read your public profile information',
|
|
37
|
+
},
|
|
38
|
+
]}
|
|
39
|
+
onAuthorize={async (grantedScopes) => {
|
|
40
|
+
const res = await authorize({ scopes: grantedScopes });
|
|
41
|
+
return { ok: res.ok };
|
|
42
|
+
}}
|
|
43
|
+
onAuthorized={() => {
|
|
44
|
+
window.location.href = resumeUrl;
|
|
45
|
+
}}
|
|
46
|
+
onDeny={() => navigate('/')}
|
|
47
|
+
labels={{
|
|
48
|
+
title: 'Authorize access',
|
|
49
|
+
requestedBy: (name) => (
|
|
50
|
+
<>
|
|
51
|
+
<strong>{name}</strong> is requesting access.
|
|
52
|
+
</>
|
|
53
|
+
),
|
|
54
|
+
permissionsHeading: 'Requested permissions',
|
|
55
|
+
approve: 'Authorize',
|
|
56
|
+
deny: 'Deny',
|
|
57
|
+
invalidRequestTitle: 'Invalid request',
|
|
58
|
+
invalidRequestBody: 'Missing required OAuth parameters. Please try again.',
|
|
59
|
+
}}
|
|
60
|
+
/>;
|
|
61
|
+
```
|
|
62
|
+
|
|
17
63
|
### Accordion
|
|
18
64
|
|
|
19
65
|
Accessible accordion component with collapsible content sections. [Docs](https://storybook.ttoss.dev/?path=/docs/components-accordion--docs)
|
package/dist/index.d.mts
CHANGED
|
@@ -854,6 +854,119 @@ declare const NotificationsMenu: ({
|
|
|
854
854
|
onClearAll
|
|
855
855
|
}: Props) => import("react/jsx-runtime").JSX.Element;
|
|
856
856
|
//#endregion
|
|
857
|
+
//#region src/components/OAuthConsent/OAuthConsent.d.ts
|
|
858
|
+
/**
|
|
859
|
+
* A single OAuth scope, optionally with implied child scopes.
|
|
860
|
+
*/
|
|
861
|
+
type ConsentScope = {
|
|
862
|
+
/** OAuth scope token, e.g. 'repo', 'write:packages', 'read'. */key: string; /** Bold display name shown on the left. Pre-translated by the consumer. */
|
|
863
|
+
label: string; /** Scope description shown below the label. Pre-translated by the consumer. */
|
|
864
|
+
description: string;
|
|
865
|
+
/**
|
|
866
|
+
* Child scopes implied by this one. When this scope is granted, every
|
|
867
|
+
* descendant is granted and locked (checked + disabled).
|
|
868
|
+
*/
|
|
869
|
+
children?: ConsentScope[];
|
|
870
|
+
/**
|
|
871
|
+
* Pre-checked on mount.
|
|
872
|
+
* @default false
|
|
873
|
+
*/
|
|
874
|
+
defaultGranted?: boolean;
|
|
875
|
+
/**
|
|
876
|
+
* Always granted; cannot be unchecked. Renders checked + disabled.
|
|
877
|
+
* @default false
|
|
878
|
+
*/
|
|
879
|
+
required?: boolean;
|
|
880
|
+
};
|
|
881
|
+
/**
|
|
882
|
+
* All visible copy for the consent screen. Pre-translated by the consumer.
|
|
883
|
+
*/
|
|
884
|
+
type OAuthConsentLabels = {
|
|
885
|
+
/** Page heading, e.g. "Authorize access". */title: string; /** Client request line. Receives the client identifier for interpolation. */
|
|
886
|
+
requestedBy: (clientName: string) => React.ReactNode; /** Section heading above the scope list, e.g. "Requested permissions". */
|
|
887
|
+
permissionsHeading: string; /** Approve button label, e.g. "Authorize". */
|
|
888
|
+
approve: string; /** Deny button label, e.g. "Deny". */
|
|
889
|
+
deny: string; /** Heading shown when required OAuth params are missing. */
|
|
890
|
+
invalidRequestTitle: string; /** Body text shown when required OAuth params are missing. */
|
|
891
|
+
invalidRequestBody: string;
|
|
892
|
+
};
|
|
893
|
+
/**
|
|
894
|
+
* Props for the OAuthConsent component.
|
|
895
|
+
*/
|
|
896
|
+
type OAuthConsentProps = {
|
|
897
|
+
/** Display name or identifier of the requesting OAuth client. */clientName: string; /** Scope tree to render. */
|
|
898
|
+
scopes: ConsentScope[];
|
|
899
|
+
/**
|
|
900
|
+
* Called when the user approves. Receives the minimal granted scope set
|
|
901
|
+
* (top-most selected keys; implied descendants omitted). Return `{ ok: true }`
|
|
902
|
+
* to signal success; the component then calls `onAuthorized`.
|
|
903
|
+
*/
|
|
904
|
+
onAuthorize: (grantedScopes: string[]) => Promise<{
|
|
905
|
+
ok: boolean;
|
|
906
|
+
}>;
|
|
907
|
+
/**
|
|
908
|
+
* Called after a successful `onAuthorize`. Use this to redirect to the OAuth
|
|
909
|
+
* server's resumed /authorize URL. The component does not navigate itself.
|
|
910
|
+
*/
|
|
911
|
+
onAuthorized: () => void; /** Called when the user clicks Deny. Use this to navigate away. */
|
|
912
|
+
onDeny: () => void;
|
|
913
|
+
/**
|
|
914
|
+
* True while the authorize call is in flight (disables both buttons).
|
|
915
|
+
* The component also tracks its own internal loading state.
|
|
916
|
+
*/
|
|
917
|
+
isAuthorizing?: boolean;
|
|
918
|
+
/**
|
|
919
|
+
* When false, renders the invalid-request error state instead of the form.
|
|
920
|
+
* @default true
|
|
921
|
+
*/
|
|
922
|
+
isValidRequest?: boolean; /** All visible copy. Pre-translated by the consumer. */
|
|
923
|
+
labels: OAuthConsentLabels;
|
|
924
|
+
};
|
|
925
|
+
/**
|
|
926
|
+
* Walk the scope tree and return the minimal set of granted scope keys.
|
|
927
|
+
* When a node is selected, its key is emitted and its subtree is skipped
|
|
928
|
+
* (the server expands parent scopes to their children). Nodes not in
|
|
929
|
+
* `selected` are recursed into so that individually-selected children
|
|
930
|
+
* are still captured.
|
|
931
|
+
*/
|
|
932
|
+
declare const collectGrantedScopes: (scopes: ConsentScope[], selected: Set<string>) => string[];
|
|
933
|
+
/**
|
|
934
|
+
* Accessible, framework-agnostic OAuth consent screen component.
|
|
935
|
+
*
|
|
936
|
+
* Renders a standards-compliant "authorize this client" page with support for
|
|
937
|
+
* flat and GitHub-style grouped/hierarchical scopes. Granting a parent scope
|
|
938
|
+
* automatically locks all descendant scopes (checked + disabled). All visible
|
|
939
|
+
* copy is injected via `labels`; no strings are hardcoded inside this component.
|
|
940
|
+
*
|
|
941
|
+
* @example
|
|
942
|
+
* ```tsx
|
|
943
|
+
* <OAuthConsent
|
|
944
|
+
* clientName="My App"
|
|
945
|
+
* scopes={[
|
|
946
|
+
* { key: 'read', label: 'Read', description: 'Read access', required: true },
|
|
947
|
+
* { key: 'write', label: 'Write', description: 'Write access' },
|
|
948
|
+
* ]}
|
|
949
|
+
* onAuthorize={async (scopes) => {
|
|
950
|
+
* const res = await authorize({ variables: { scopes } });
|
|
951
|
+
* return { ok: !!res.ok };
|
|
952
|
+
* }}
|
|
953
|
+
* onAuthorized={() => { window.location.href = resumeUrl; }}
|
|
954
|
+
* onDeny={() => navigate('/')}
|
|
955
|
+
* labels={labels}
|
|
956
|
+
* />
|
|
957
|
+
* ```
|
|
958
|
+
*/
|
|
959
|
+
declare const OAuthConsent: ({
|
|
960
|
+
clientName,
|
|
961
|
+
scopes,
|
|
962
|
+
onAuthorize,
|
|
963
|
+
onAuthorized,
|
|
964
|
+
onDeny,
|
|
965
|
+
isAuthorizing,
|
|
966
|
+
isValidRequest,
|
|
967
|
+
labels
|
|
968
|
+
}: OAuthConsentProps) => import("react/jsx-runtime").JSX.Element;
|
|
969
|
+
//#endregion
|
|
857
970
|
//#region src/components/Search/Search.d.ts
|
|
858
971
|
type SearchProps = Omit<InputProps, 'onChange'> & {
|
|
859
972
|
loading?: boolean;
|
|
@@ -926,4 +1039,4 @@ declare const Tabs: {
|
|
|
926
1039
|
declare const ToastContainer: (props: ToastContainerProps) => import("react/jsx-runtime").JSX.Element;
|
|
927
1040
|
declare const toast: typeof toast$1;
|
|
928
1041
|
//#endregion
|
|
929
|
-
export { Accordion, AccordionItem, AccordionProps, AccordionRenderItemProps, BaseMetric, DateMetric, DatePicker, DateRange, Drawer, DrawerProps, EnhancedTitle, EnhancedTitleBadge, EnhancedTitleProps, EnhancedTitleVariant, FileUploadState, FileUploader, FileUploaderProps, InstallPwa, InstallPwaUi, InstallPwaUiProps, JsonEditor, JsonView, type LinkComponentProps, List, ListItem, ListItemProps, ListProps, LockedOverlay, Markdown, MarkdownProps, Menu, MenuProps, Metric, MetricCard, MetricCardProps, Modal, ModalProps, NavList, type NavListGroup, type NavListItem, type NavListProps, Notification, NotificationAction, NotificationButton, NotificationButtonProps, NotificationCard, NotificationCardProps, NotificationsMenu, NumberMetric, OnFilesChange, OnRemove, OnUpload, OnUploadComplete, OnUploadError, OnUploadProgress, OnUploadStart, PercentageMetric, Search, SearchProps, SpotlightCard, SpotlightCardProps, type TabListProps, type TabPanelProps, type TabProps, Table, Tabs, type TabsProps, ToastContainer, type ToastContainerProps, type ToastOptions, UploadResult, UploadedFile, createCell, createColumnHelper, flexRender, getCoreRowModel, toast, useReactTable };
|
|
1042
|
+
export { Accordion, AccordionItem, AccordionProps, AccordionRenderItemProps, BaseMetric, ConsentScope, DateMetric, DatePicker, DateRange, Drawer, DrawerProps, EnhancedTitle, EnhancedTitleBadge, EnhancedTitleProps, EnhancedTitleVariant, FileUploadState, FileUploader, FileUploaderProps, InstallPwa, InstallPwaUi, InstallPwaUiProps, JsonEditor, JsonView, type LinkComponentProps, List, ListItem, ListItemProps, ListProps, LockedOverlay, Markdown, MarkdownProps, Menu, MenuProps, Metric, MetricCard, MetricCardProps, Modal, ModalProps, NavList, type NavListGroup, type NavListItem, type NavListProps, Notification, NotificationAction, NotificationButton, NotificationButtonProps, NotificationCard, NotificationCardProps, NotificationsMenu, NumberMetric, OAuthConsent, OAuthConsentLabels, OAuthConsentProps, OnFilesChange, OnRemove, OnUpload, OnUploadComplete, OnUploadError, OnUploadProgress, OnUploadStart, PercentageMetric, Search, SearchProps, SpotlightCard, SpotlightCardProps, type TabListProps, type TabPanelProps, type TabProps, Table, Tabs, type TabsProps, ToastContainer, type ToastContainerProps, type ToastOptions, UploadResult, UploadedFile, collectGrantedScopes, createCell, createColumnHelper, flexRender, getCoreRowModel, toast, useReactTable };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
|
|
2
|
-
import { Badge, BaseStyles, Box, Button, Card, CloseButton, Divider, Flex, Heading, IconButton, Input, Label, Link, Stack, Tag, Text, TooltipIcon, keyframes, useResponsiveValue, useTheme } from "@ttoss/ui";
|
|
2
|
+
import { Badge, BaseStyles, Box, Button, Card, Checkbox, CloseButton, Divider, Flex, Heading, IconButton, Input, Label, Link, Stack, Tag, Text, TooltipIcon, keyframes, useResponsiveValue, useTheme } from "@ttoss/ui";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
import { Icon } from "@ttoss/react-icons";
|
|
@@ -2980,6 +2980,331 @@ var NotificationsMenu = ({
|
|
|
2980
2980
|
});
|
|
2981
2981
|
};
|
|
2982
2982
|
|
|
2983
|
+
//#endregion
|
|
2984
|
+
//#region src/components/OAuthConsent/OAuthConsent.tsx
|
|
2985
|
+
/**
|
|
2986
|
+
* Walk the scope tree and return the minimal set of granted scope keys.
|
|
2987
|
+
* When a node is selected, its key is emitted and its subtree is skipped
|
|
2988
|
+
* (the server expands parent scopes to their children). Nodes not in
|
|
2989
|
+
* `selected` are recursed into so that individually-selected children
|
|
2990
|
+
* are still captured.
|
|
2991
|
+
*/
|
|
2992
|
+
var collectGrantedScopes = (scopes, selected) => {
|
|
2993
|
+
const result = [];
|
|
2994
|
+
const walk = nodes => {
|
|
2995
|
+
for (const node of nodes) if (selected.has(node.key)) result.push(node.key);else if (node.children) walk(node.children);
|
|
2996
|
+
};
|
|
2997
|
+
walk(scopes);
|
|
2998
|
+
return result;
|
|
2999
|
+
};
|
|
3000
|
+
var initSelected = scopes => {
|
|
3001
|
+
const selected = /* @__PURE__ */new Set();
|
|
3002
|
+
const walk = nodes => {
|
|
3003
|
+
for (const node of nodes) {
|
|
3004
|
+
if (node.required || node.defaultGranted) selected.add(node.key);
|
|
3005
|
+
if (node.children) walk(node.children);
|
|
3006
|
+
}
|
|
3007
|
+
};
|
|
3008
|
+
walk(scopes);
|
|
3009
|
+
return selected;
|
|
3010
|
+
};
|
|
3011
|
+
var getScopeRowState = ({
|
|
3012
|
+
scope,
|
|
3013
|
+
ancestorSelected,
|
|
3014
|
+
selected,
|
|
3015
|
+
busy
|
|
3016
|
+
}) => {
|
|
3017
|
+
const isChecked = ancestorSelected || selected.has(scope.key);
|
|
3018
|
+
const isLocked = !!scope.required || ancestorSelected;
|
|
3019
|
+
return {
|
|
3020
|
+
isChecked,
|
|
3021
|
+
isDisabled: isLocked || busy,
|
|
3022
|
+
isInteractive: !isLocked && !busy
|
|
3023
|
+
};
|
|
3024
|
+
};
|
|
3025
|
+
var ScopeRow = ({
|
|
3026
|
+
scope,
|
|
3027
|
+
ancestorSelected,
|
|
3028
|
+
selected,
|
|
3029
|
+
busy,
|
|
3030
|
+
onToggle
|
|
3031
|
+
}) => {
|
|
3032
|
+
const {
|
|
3033
|
+
isChecked,
|
|
3034
|
+
isDisabled,
|
|
3035
|
+
isInteractive
|
|
3036
|
+
} = getScopeRowState({
|
|
3037
|
+
scope,
|
|
3038
|
+
ancestorSelected,
|
|
3039
|
+
selected,
|
|
3040
|
+
busy
|
|
3041
|
+
});
|
|
3042
|
+
const checkboxId = `consent-scope-${scope.key}`;
|
|
3043
|
+
const labelId = `consent-scope-label-${scope.key}`;
|
|
3044
|
+
const descId = `consent-scope-desc-${scope.key}`;
|
|
3045
|
+
return /* @__PURE__ */jsxs(Box, {
|
|
3046
|
+
as: "li",
|
|
3047
|
+
sx: {
|
|
3048
|
+
listStyle: "none"
|
|
3049
|
+
},
|
|
3050
|
+
children: [/* @__PURE__ */jsxs(Flex, {
|
|
3051
|
+
as: "label",
|
|
3052
|
+
sx: {
|
|
3053
|
+
gap: 3,
|
|
3054
|
+
alignItems: "flex-start",
|
|
3055
|
+
paddingX: 3,
|
|
3056
|
+
paddingY: 3,
|
|
3057
|
+
borderRadius: "md",
|
|
3058
|
+
opacity: ancestorSelected ? .55 : 1,
|
|
3059
|
+
transition: "background-color 0.15s ease",
|
|
3060
|
+
cursor: isInteractive ? "pointer" : "default",
|
|
3061
|
+
...(isInteractive && {
|
|
3062
|
+
"&:hover": {
|
|
3063
|
+
backgroundColor: "display.background.muted.default"
|
|
3064
|
+
}
|
|
3065
|
+
})
|
|
3066
|
+
},
|
|
3067
|
+
children: [/* @__PURE__ */jsx(Box, {
|
|
3068
|
+
sx: {
|
|
3069
|
+
paddingTop: "3px",
|
|
3070
|
+
flexShrink: 0
|
|
3071
|
+
},
|
|
3072
|
+
children: /* @__PURE__ */jsx(Checkbox, {
|
|
3073
|
+
id: checkboxId,
|
|
3074
|
+
checked: isChecked,
|
|
3075
|
+
disabled: isDisabled,
|
|
3076
|
+
"aria-labelledby": labelId,
|
|
3077
|
+
"aria-describedby": descId,
|
|
3078
|
+
onChange: () => {
|
|
3079
|
+
onToggle(scope, ancestorSelected);
|
|
3080
|
+
}
|
|
3081
|
+
})
|
|
3082
|
+
}), /* @__PURE__ */jsxs(Box, {
|
|
3083
|
+
sx: {
|
|
3084
|
+
flex: 1,
|
|
3085
|
+
minWidth: 0
|
|
3086
|
+
},
|
|
3087
|
+
children: [/* @__PURE__ */jsx(Text, {
|
|
3088
|
+
as: "span",
|
|
3089
|
+
id: labelId,
|
|
3090
|
+
sx: {
|
|
3091
|
+
fontWeight: "semibold",
|
|
3092
|
+
fontSize: "md",
|
|
3093
|
+
lineHeight: "short",
|
|
3094
|
+
color: "display.text.primary.default",
|
|
3095
|
+
display: "block"
|
|
3096
|
+
},
|
|
3097
|
+
children: scope.label
|
|
3098
|
+
}), /* @__PURE__ */jsx(Text, {
|
|
3099
|
+
id: descId,
|
|
3100
|
+
sx: {
|
|
3101
|
+
fontSize: "sm",
|
|
3102
|
+
lineHeight: "short",
|
|
3103
|
+
color: "display.text.muted.default",
|
|
3104
|
+
display: "block",
|
|
3105
|
+
marginTop: "1"
|
|
3106
|
+
},
|
|
3107
|
+
children: scope.description
|
|
3108
|
+
})]
|
|
3109
|
+
})]
|
|
3110
|
+
}), scope.children?.length ? /* @__PURE__ */jsx(Box, {
|
|
3111
|
+
as: "ul",
|
|
3112
|
+
sx: {
|
|
3113
|
+
listStyle: "none",
|
|
3114
|
+
margin: 0,
|
|
3115
|
+
padding: 0,
|
|
3116
|
+
marginLeft: 5,
|
|
3117
|
+
paddingLeft: 2,
|
|
3118
|
+
borderLeft: "sm",
|
|
3119
|
+
borderColor: "display.border.muted.default"
|
|
3120
|
+
},
|
|
3121
|
+
children: scope.children?.map(child => {
|
|
3122
|
+
return /* @__PURE__ */jsx(ScopeRow, {
|
|
3123
|
+
scope: child,
|
|
3124
|
+
ancestorSelected: isChecked,
|
|
3125
|
+
selected,
|
|
3126
|
+
busy,
|
|
3127
|
+
onToggle
|
|
3128
|
+
}, child.key);
|
|
3129
|
+
})
|
|
3130
|
+
}) : null]
|
|
3131
|
+
});
|
|
3132
|
+
};
|
|
3133
|
+
/** Shared centered card shell for the consent and invalid-request views. */
|
|
3134
|
+
var ConsentCard = ({
|
|
3135
|
+
children
|
|
3136
|
+
}) => {
|
|
3137
|
+
return /* @__PURE__ */jsx(Flex, {
|
|
3138
|
+
sx: {
|
|
3139
|
+
justifyContent: "center",
|
|
3140
|
+
paddingX: 4,
|
|
3141
|
+
paddingY: 6
|
|
3142
|
+
},
|
|
3143
|
+
children: /* @__PURE__ */jsx(Card, {
|
|
3144
|
+
sx: {
|
|
3145
|
+
width: "100%",
|
|
3146
|
+
maxWidth: 480,
|
|
3147
|
+
alignItems: "stretch",
|
|
3148
|
+
padding: 6
|
|
3149
|
+
},
|
|
3150
|
+
children
|
|
3151
|
+
})
|
|
3152
|
+
});
|
|
3153
|
+
};
|
|
3154
|
+
/**
|
|
3155
|
+
* Accessible, framework-agnostic OAuth consent screen component.
|
|
3156
|
+
*
|
|
3157
|
+
* Renders a standards-compliant "authorize this client" page with support for
|
|
3158
|
+
* flat and GitHub-style grouped/hierarchical scopes. Granting a parent scope
|
|
3159
|
+
* automatically locks all descendant scopes (checked + disabled). All visible
|
|
3160
|
+
* copy is injected via `labels`; no strings are hardcoded inside this component.
|
|
3161
|
+
*
|
|
3162
|
+
* @example
|
|
3163
|
+
* ```tsx
|
|
3164
|
+
* <OAuthConsent
|
|
3165
|
+
* clientName="My App"
|
|
3166
|
+
* scopes={[
|
|
3167
|
+
* { key: 'read', label: 'Read', description: 'Read access', required: true },
|
|
3168
|
+
* { key: 'write', label: 'Write', description: 'Write access' },
|
|
3169
|
+
* ]}
|
|
3170
|
+
* onAuthorize={async (scopes) => {
|
|
3171
|
+
* const res = await authorize({ variables: { scopes } });
|
|
3172
|
+
* return { ok: !!res.ok };
|
|
3173
|
+
* }}
|
|
3174
|
+
* onAuthorized={() => { window.location.href = resumeUrl; }}
|
|
3175
|
+
* onDeny={() => navigate('/')}
|
|
3176
|
+
* labels={labels}
|
|
3177
|
+
* />
|
|
3178
|
+
* ```
|
|
3179
|
+
*/
|
|
3180
|
+
var OAuthConsent = ({
|
|
3181
|
+
clientName,
|
|
3182
|
+
scopes,
|
|
3183
|
+
onAuthorize,
|
|
3184
|
+
onAuthorized,
|
|
3185
|
+
onDeny,
|
|
3186
|
+
isAuthorizing = false,
|
|
3187
|
+
isValidRequest = true,
|
|
3188
|
+
labels
|
|
3189
|
+
}) => {
|
|
3190
|
+
const [selected, setSelected] = React.useState(() => {
|
|
3191
|
+
return initSelected(scopes);
|
|
3192
|
+
});
|
|
3193
|
+
const [isLoading, setIsLoading] = React.useState(false);
|
|
3194
|
+
const busy = isAuthorizing || isLoading;
|
|
3195
|
+
const handleToggle = React.useCallback((scope, ancestorSelected) => {
|
|
3196
|
+
if (scope.required || ancestorSelected) return;
|
|
3197
|
+
setSelected(prev => {
|
|
3198
|
+
const next = new Set(prev);
|
|
3199
|
+
if (next.has(scope.key)) next.delete(scope.key);else next.add(scope.key);
|
|
3200
|
+
return next;
|
|
3201
|
+
});
|
|
3202
|
+
}, []);
|
|
3203
|
+
const handleApprove = React.useCallback(async () => {
|
|
3204
|
+
const grantedScopes = collectGrantedScopes(scopes, selected);
|
|
3205
|
+
setIsLoading(true);
|
|
3206
|
+
try {
|
|
3207
|
+
if ((await onAuthorize(grantedScopes)).ok) onAuthorized();
|
|
3208
|
+
} finally {
|
|
3209
|
+
setIsLoading(false);
|
|
3210
|
+
}
|
|
3211
|
+
}, [scopes, selected, onAuthorize, onAuthorized]);
|
|
3212
|
+
if (!isValidRequest) return /* @__PURE__ */jsxs(ConsentCard, {
|
|
3213
|
+
children: [/* @__PURE__ */jsx(Heading, {
|
|
3214
|
+
as: "h1",
|
|
3215
|
+
sx: {
|
|
3216
|
+
fontSize: "3xl",
|
|
3217
|
+
color: "display.text.primary.default",
|
|
3218
|
+
marginBottom: 3
|
|
3219
|
+
},
|
|
3220
|
+
children: labels.invalidRequestTitle
|
|
3221
|
+
}), /* @__PURE__ */jsx(Text, {
|
|
3222
|
+
sx: {
|
|
3223
|
+
fontSize: "md",
|
|
3224
|
+
lineHeight: "moderate",
|
|
3225
|
+
color: "display.text.secondary.default"
|
|
3226
|
+
},
|
|
3227
|
+
children: labels.invalidRequestBody
|
|
3228
|
+
})]
|
|
3229
|
+
});
|
|
3230
|
+
return /* @__PURE__ */jsxs(ConsentCard, {
|
|
3231
|
+
children: [/* @__PURE__ */jsx(Heading, {
|
|
3232
|
+
as: "h1",
|
|
3233
|
+
sx: {
|
|
3234
|
+
fontSize: "3xl",
|
|
3235
|
+
lineHeight: "shorter",
|
|
3236
|
+
color: "display.text.primary.default",
|
|
3237
|
+
marginBottom: 2
|
|
3238
|
+
},
|
|
3239
|
+
children: labels.title
|
|
3240
|
+
}), /* @__PURE__ */jsx(Text, {
|
|
3241
|
+
sx: {
|
|
3242
|
+
fontSize: "md",
|
|
3243
|
+
lineHeight: "moderate",
|
|
3244
|
+
color: "display.text.secondary.default"
|
|
3245
|
+
},
|
|
3246
|
+
children: labels.requestedBy(clientName)
|
|
3247
|
+
}), /* @__PURE__ */jsx(Divider, {
|
|
3248
|
+
sx: {
|
|
3249
|
+
marginY: 5,
|
|
3250
|
+
borderColor: "display.border.muted.default"
|
|
3251
|
+
}
|
|
3252
|
+
}), /* @__PURE__ */jsx(Heading, {
|
|
3253
|
+
as: "h3",
|
|
3254
|
+
sx: {
|
|
3255
|
+
fontSize: "xs",
|
|
3256
|
+
fontWeight: "semibold",
|
|
3257
|
+
letterSpacing: "wider",
|
|
3258
|
+
textTransform: "uppercase",
|
|
3259
|
+
color: "display.text.muted.default",
|
|
3260
|
+
marginBottom: 2
|
|
3261
|
+
},
|
|
3262
|
+
children: labels.permissionsHeading
|
|
3263
|
+
}), /* @__PURE__ */jsx(Box, {
|
|
3264
|
+
as: "ul",
|
|
3265
|
+
sx: {
|
|
3266
|
+
listStyle: "none",
|
|
3267
|
+
margin: 0,
|
|
3268
|
+
padding: 0
|
|
3269
|
+
},
|
|
3270
|
+
children: scopes.map(scope => {
|
|
3271
|
+
return /* @__PURE__ */jsx(ScopeRow, {
|
|
3272
|
+
scope,
|
|
3273
|
+
ancestorSelected: false,
|
|
3274
|
+
selected,
|
|
3275
|
+
busy,
|
|
3276
|
+
onToggle: handleToggle
|
|
3277
|
+
}, scope.key);
|
|
3278
|
+
})
|
|
3279
|
+
}), /* @__PURE__ */jsxs(Flex, {
|
|
3280
|
+
sx: {
|
|
3281
|
+
gap: 3,
|
|
3282
|
+
marginTop: 6
|
|
3283
|
+
},
|
|
3284
|
+
children: [/* @__PURE__ */jsx(Button, {
|
|
3285
|
+
variant: "secondary",
|
|
3286
|
+
onClick: onDeny,
|
|
3287
|
+
disabled: busy,
|
|
3288
|
+
sx: {
|
|
3289
|
+
flex: 1,
|
|
3290
|
+
justifyContent: "center"
|
|
3291
|
+
},
|
|
3292
|
+
children: labels.deny
|
|
3293
|
+
}), /* @__PURE__ */jsx(Button, {
|
|
3294
|
+
variant: "accent",
|
|
3295
|
+
onClick: handleApprove,
|
|
3296
|
+
disabled: busy,
|
|
3297
|
+
loading: isLoading || isAuthorizing,
|
|
3298
|
+
sx: {
|
|
3299
|
+
flex: 1,
|
|
3300
|
+
justifyContent: "center"
|
|
3301
|
+
},
|
|
3302
|
+
children: labels.approve
|
|
3303
|
+
})]
|
|
3304
|
+
})]
|
|
3305
|
+
});
|
|
3306
|
+
};
|
|
3307
|
+
|
|
2983
3308
|
//#endregion
|
|
2984
3309
|
//#region src/components/Search/Search.tsx
|
|
2985
3310
|
var Search = ({
|
|
@@ -3178,4 +3503,4 @@ var ToastContainer = props => {
|
|
|
3178
3503
|
var toast = toast$1;
|
|
3179
3504
|
|
|
3180
3505
|
//#endregion
|
|
3181
|
-
export { Accordion, DatePicker, Drawer, EnhancedTitle, FileUploader, InstallPwa, InstallPwaUi, JsonEditor, JsonView, List, ListItem, LockedOverlay, Markdown, Menu, MetricCard, Modal, NavList, NotificationButton, NotificationCard, NotificationsMenu, Search, SpotlightCard, Table, Tabs, ToastContainer, createCell, createColumnHelper, flexRender, getCoreRowModel, toast, useReactTable };
|
|
3506
|
+
export { Accordion, DatePicker, Drawer, EnhancedTitle, FileUploader, InstallPwa, InstallPwaUi, JsonEditor, JsonView, List, ListItem, LockedOverlay, Markdown, Menu, MetricCard, Modal, NavList, NotificationButton, NotificationCard, NotificationsMenu, OAuthConsent, Search, SpotlightCard, Table, Tabs, ToastContainer, collectGrantedScopes, createCell, createColumnHelper, flexRender, getCoreRowModel, toast, useReactTable };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttoss/components",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.16.1",
|
|
4
4
|
"description": "React components for ttoss ecosystem.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"React",
|
|
@@ -55,11 +55,11 @@
|
|
|
55
55
|
"tsx": "^4.21.0",
|
|
56
56
|
"@ttoss/config": "^1.37.17",
|
|
57
57
|
"@ttoss/i18n-cli": "^0.8.17",
|
|
58
|
-
"@ttoss/react-hooks": "^2.3.20",
|
|
59
58
|
"@ttoss/react-i18n": "^2.3.0",
|
|
60
59
|
"@ttoss/react-icons": "^0.7.21",
|
|
61
|
-
"@ttoss/
|
|
62
|
-
"@ttoss/test-utils": "^4.2.18"
|
|
60
|
+
"@ttoss/react-hooks": "^2.3.20",
|
|
61
|
+
"@ttoss/test-utils": "^4.2.18",
|
|
62
|
+
"@ttoss/ui": "^6.9.28"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
65
|
"react": ">=16.8.0",
|