mirror of
https://github.com/logto-io/logto.git
synced 2025-01-06 20:40:08 -05:00
refactor(console): resources table (#2482)
This commit is contained in:
parent
59c52feb2c
commit
6377c4837f
8 changed files with 327 additions and 319 deletions
|
@ -86,58 +86,60 @@ const AuditLogTable = ({ userId }: Props) => {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classNames(resourcesStyles.table, tableStyles.scrollable)}>
|
||||
<table className={classNames(logs?.length === 0 && tableStyles.empty)}>
|
||||
<colgroup>
|
||||
<col className={styles.eventName} />
|
||||
{showUserColumn && <col />}
|
||||
<col />
|
||||
<col />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t('logs.event')}</th>
|
||||
{showUserColumn && <th>{t('logs.user')}</th>}
|
||||
<th>{t('logs.application')}</th>
|
||||
<th>{t('logs.time')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{!data && error && (
|
||||
<TableError
|
||||
columns={4}
|
||||
content={error.body?.message ?? error.message}
|
||||
onRetry={async () => mutate(undefined, true)}
|
||||
/>
|
||||
)}
|
||||
{isLoading && <TableLoading columns={4} />}
|
||||
{logs?.length === 0 && <TableEmpty columns={4} />}
|
||||
{logs?.map(({ type, payload, createdAt, id }) => (
|
||||
<tr
|
||||
key={id}
|
||||
className={tableStyles.clickable}
|
||||
onClick={() => {
|
||||
navigate(`${pathname}/${id}`);
|
||||
}}
|
||||
>
|
||||
<td>
|
||||
<EventName type={type} isSuccess={payload.result === LogResult.Success} />
|
||||
</td>
|
||||
{showUserColumn && (
|
||||
<td>{payload.userId ? <UserName userId={payload.userId} /> : '-'}</td>
|
||||
)}
|
||||
<td>
|
||||
{payload.applicationId ? (
|
||||
<ApplicationName applicationId={payload.applicationId} />
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</td>
|
||||
<td>{new Date(createdAt).toLocaleString()}</td>
|
||||
<div className={resourcesStyles.table}>
|
||||
<div className={tableStyles.scrollable}>
|
||||
<table className={classNames(logs?.length === 0 && tableStyles.empty)}>
|
||||
<colgroup>
|
||||
<col className={styles.eventName} />
|
||||
{showUserColumn && <col />}
|
||||
<col />
|
||||
<col />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t('logs.event')}</th>
|
||||
{showUserColumn && <th>{t('logs.user')}</th>}
|
||||
<th>{t('logs.application')}</th>
|
||||
<th>{t('logs.time')}</th>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
{!data && error && (
|
||||
<TableError
|
||||
columns={4}
|
||||
content={error.body?.message ?? error.message}
|
||||
onRetry={async () => mutate(undefined, true)}
|
||||
/>
|
||||
)}
|
||||
{isLoading && <TableLoading columns={4} />}
|
||||
{logs?.length === 0 && <TableEmpty columns={4} />}
|
||||
{logs?.map(({ type, payload, createdAt, id }) => (
|
||||
<tr
|
||||
key={id}
|
||||
className={tableStyles.clickable}
|
||||
onClick={() => {
|
||||
navigate(`${pathname}/${id}`);
|
||||
}}
|
||||
>
|
||||
<td>
|
||||
<EventName type={type} isSuccess={payload.result === LogResult.Success} />
|
||||
</td>
|
||||
{showUserColumn && (
|
||||
<td>{payload.userId ? <UserName userId={payload.userId} /> : '-'}</td>
|
||||
)}
|
||||
<td>
|
||||
{payload.applicationId ? (
|
||||
<ApplicationName applicationId={payload.applicationId} />
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</td>
|
||||
<td>{new Date(createdAt).toLocaleString()}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<Pagination
|
||||
pageIndex={pageIndex}
|
||||
|
|
|
@ -77,65 +77,67 @@ const ApiResources = () => {
|
|||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
<div className={classNames(resourcesStyles.table, tableStyles.scrollable)}>
|
||||
<table className={classNames(!data && tableStyles.empty)}>
|
||||
<colgroup>
|
||||
<col className={styles.apiResourceName} />
|
||||
<col />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t('api_resources.api_name')}</th>
|
||||
<th>{t('api_resources.api_identifier')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{!data && error && (
|
||||
<TableError
|
||||
columns={2}
|
||||
content={error.body?.message ?? error.message}
|
||||
onRetry={async () => mutate(undefined, true)}
|
||||
/>
|
||||
)}
|
||||
{isLoading && <TableLoading columns={2} />}
|
||||
{apiResources?.length === 0 && (
|
||||
<TableEmpty columns={2}>
|
||||
<Button
|
||||
title="api_resources.create"
|
||||
type="outline"
|
||||
onClick={() => {
|
||||
setIsCreateFormOpen(true);
|
||||
}}
|
||||
<div className={resourcesStyles.table}>
|
||||
<div className={tableStyles.scrollable}>
|
||||
<table className={classNames(!data && tableStyles.empty)}>
|
||||
<colgroup>
|
||||
<col className={styles.apiResourceName} />
|
||||
<col />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t('api_resources.api_name')}</th>
|
||||
<th>{t('api_resources.api_identifier')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{!data && error && (
|
||||
<TableError
|
||||
columns={2}
|
||||
content={error.body?.message ?? error.message}
|
||||
onRetry={async () => mutate(undefined, true)}
|
||||
/>
|
||||
</TableEmpty>
|
||||
)}
|
||||
{apiResources?.map(({ id, name, indicator }) => {
|
||||
const ResourceIcon =
|
||||
theme === AppearanceMode.LightMode ? ApiResource : ApiResourceDark;
|
||||
)}
|
||||
{isLoading && <TableLoading columns={2} />}
|
||||
{apiResources?.length === 0 && (
|
||||
<TableEmpty columns={2}>
|
||||
<Button
|
||||
title="api_resources.create"
|
||||
type="outline"
|
||||
onClick={() => {
|
||||
setIsCreateFormOpen(true);
|
||||
}}
|
||||
/>
|
||||
</TableEmpty>
|
||||
)}
|
||||
{apiResources?.map(({ id, name, indicator }) => {
|
||||
const ResourceIcon =
|
||||
theme === AppearanceMode.LightMode ? ApiResource : ApiResourceDark;
|
||||
|
||||
return (
|
||||
<tr
|
||||
key={id}
|
||||
className={tableStyles.clickable}
|
||||
onClick={() => {
|
||||
navigate(buildDetailsLink(id));
|
||||
}}
|
||||
>
|
||||
<td>
|
||||
<ItemPreview
|
||||
title={name}
|
||||
icon={<ResourceIcon className={styles.icon} />}
|
||||
to={buildDetailsLink(id)}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<CopyToClipboard value={indicator} variant="text" />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
return (
|
||||
<tr
|
||||
key={id}
|
||||
className={tableStyles.clickable}
|
||||
onClick={() => {
|
||||
navigate(buildDetailsLink(id));
|
||||
}}
|
||||
>
|
||||
<td>
|
||||
<ItemPreview
|
||||
title={name}
|
||||
icon={<ResourceIcon className={styles.icon} />}
|
||||
to={buildDetailsLink(id)}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<CopyToClipboard value={indicator} variant="text" />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<Pagination
|
||||
pageIndex={pageIndex}
|
||||
|
|
|
@ -70,63 +70,65 @@ const Applications = () => {
|
|||
/>
|
||||
</Modal>
|
||||
</div>{' '}
|
||||
<div className={classNames(resourcesStyles.table, tableStyles.scrollable)}>
|
||||
<table className={classNames(!data && tableStyles.empty)}>
|
||||
<colgroup>
|
||||
<col className={styles.applicationName} />
|
||||
<col />
|
||||
</colgroup>
|
||||
<div className={resourcesStyles.table}>
|
||||
<div className={tableStyles.scrollable}>
|
||||
<table className={classNames(!data && tableStyles.empty)}>
|
||||
<colgroup>
|
||||
<col className={styles.applicationName} />
|
||||
<col />
|
||||
</colgroup>
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t('applications.application_name')}</th>
|
||||
<th>{t('applications.app_id')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{!data && error && (
|
||||
<TableError
|
||||
columns={2}
|
||||
content={error.body?.message ?? error.message}
|
||||
onRetry={async () => mutate(undefined, true)}
|
||||
/>
|
||||
)}
|
||||
{isLoading && <TableLoading columns={2} />}
|
||||
{applications?.length === 0 && (
|
||||
<TableEmpty columns={2}>
|
||||
<Button
|
||||
title="applications.create"
|
||||
type="outline"
|
||||
onClick={() => {
|
||||
navigate('/applications/create');
|
||||
}}
|
||||
/>
|
||||
</TableEmpty>
|
||||
)}
|
||||
{applications?.map(({ id, name, type }) => (
|
||||
<tr
|
||||
key={id}
|
||||
className={tableStyles.clickable}
|
||||
onClick={() => {
|
||||
navigate(`/applications/${id}`);
|
||||
}}
|
||||
>
|
||||
<td>
|
||||
<ItemPreview
|
||||
title={name}
|
||||
subtitle={t(`${applicationTypeI18nKey[type]}.title`)}
|
||||
icon={<ApplicationIcon className={styles.icon} type={type} />}
|
||||
to={`/applications/${id}`}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<CopyToClipboard value={id} variant="text" />
|
||||
</td>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t('applications.application_name')}</th>
|
||||
<th>{t('applications.app_id')}</th>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{!data && error && (
|
||||
<TableError
|
||||
columns={2}
|
||||
content={error.body?.message ?? error.message}
|
||||
onRetry={async () => mutate(undefined, true)}
|
||||
/>
|
||||
)}
|
||||
{isLoading && <TableLoading columns={2} />}
|
||||
{applications?.length === 0 && (
|
||||
<TableEmpty columns={2}>
|
||||
<Button
|
||||
title="applications.create"
|
||||
type="outline"
|
||||
onClick={() => {
|
||||
navigate('/applications/create');
|
||||
}}
|
||||
/>
|
||||
</TableEmpty>
|
||||
)}
|
||||
{applications?.map(({ id, name, type }) => (
|
||||
<tr
|
||||
key={id}
|
||||
className={tableStyles.clickable}
|
||||
onClick={() => {
|
||||
navigate(`/applications/${id}`);
|
||||
}}
|
||||
>
|
||||
<td>
|
||||
<ItemPreview
|
||||
title={name}
|
||||
subtitle={t(`${applicationTypeI18nKey[type]}.title`)}
|
||||
icon={<ApplicationIcon className={styles.icon} type={type} />}
|
||||
to={`/applications/${id}`}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<CopyToClipboard value={id} variant="text" />
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<Pagination
|
||||
pageIndex={pageIndex}
|
||||
|
|
|
@ -80,70 +80,72 @@ const Connectors = () => {
|
|||
<TabNavItem href="/connectors">{t('connectors.tab_email_sms')}</TabNavItem>
|
||||
<TabNavItem href="/connectors/social">{t('connectors.tab_social')}</TabNavItem>
|
||||
</TabNav>
|
||||
<div className={classNames(resourcesStyles.table, tableStyles.scrollable)}>
|
||||
<table className={classNames(!data && tableStyles.empty)}>
|
||||
<colgroup>
|
||||
<col className={styles.connectorName} />
|
||||
<col />
|
||||
<col />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t('connectors.connector_name')}</th>
|
||||
<th>{t('connectors.connector_type')}</th>
|
||||
<th>
|
||||
<ConnectorStatusField />
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{!data && error && (
|
||||
<TableError
|
||||
columns={3}
|
||||
content={error.body?.message ?? error.message}
|
||||
onRetry={async () => mutate(undefined, true)}
|
||||
/>
|
||||
)}
|
||||
{isLoading && <TableLoading columns={3} />}
|
||||
{socialConnectorGroups?.length === 0 && (
|
||||
<TableEmpty
|
||||
columns={3}
|
||||
title={t('connectors.type.social')}
|
||||
content={t('connectors.social_connector_eg')}
|
||||
image={isLightMode ? <SocialConnectorEmpty /> : <SocialConnectorEmptyDark />}
|
||||
>
|
||||
<Button
|
||||
title="connectors.create"
|
||||
type="outline"
|
||||
onClick={() => {
|
||||
setCreateType(ConnectorType.Social);
|
||||
<div className={resourcesStyles.table}>
|
||||
<div className={tableStyles.scrollable}>
|
||||
<table className={classNames(!data && tableStyles.empty)}>
|
||||
<colgroup>
|
||||
<col className={styles.connectorName} />
|
||||
<col />
|
||||
<col />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t('connectors.connector_name')}</th>
|
||||
<th>{t('connectors.connector_type')}</th>
|
||||
<th>
|
||||
<ConnectorStatusField />
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{!data && error && (
|
||||
<TableError
|
||||
columns={3}
|
||||
content={error.body?.message ?? error.message}
|
||||
onRetry={async () => mutate(undefined, true)}
|
||||
/>
|
||||
)}
|
||||
{isLoading && <TableLoading columns={3} />}
|
||||
{socialConnectorGroups?.length === 0 && (
|
||||
<TableEmpty
|
||||
columns={3}
|
||||
title={t('connectors.type.social')}
|
||||
content={t('connectors.social_connector_eg')}
|
||||
image={isLightMode ? <SocialConnectorEmpty /> : <SocialConnectorEmptyDark />}
|
||||
>
|
||||
<Button
|
||||
title="connectors.create"
|
||||
type="outline"
|
||||
onClick={() => {
|
||||
setCreateType(ConnectorType.Social);
|
||||
}}
|
||||
/>
|
||||
</TableEmpty>
|
||||
)}
|
||||
{!isLoading && !isSocial && (
|
||||
<ConnectorRow
|
||||
connectors={smsConnector ? [smsConnector] : []}
|
||||
type={ConnectorType.Sms}
|
||||
onClickSetup={() => {
|
||||
setCreateType(ConnectorType.Sms);
|
||||
}}
|
||||
/>
|
||||
</TableEmpty>
|
||||
)}
|
||||
{!isLoading && !isSocial && (
|
||||
<ConnectorRow
|
||||
connectors={smsConnector ? [smsConnector] : []}
|
||||
type={ConnectorType.Sms}
|
||||
onClickSetup={() => {
|
||||
setCreateType(ConnectorType.Sms);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!isLoading && !isSocial && (
|
||||
<ConnectorRow
|
||||
connectors={emailConnector ? [emailConnector] : []}
|
||||
type={ConnectorType.Email}
|
||||
onClickSetup={() => {
|
||||
setCreateType(ConnectorType.Email);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{socialConnectorGroups?.map(({ connectors, id }) => (
|
||||
<ConnectorRow key={id} connectors={connectors} type={ConnectorType.Social} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
{!isLoading && !isSocial && (
|
||||
<ConnectorRow
|
||||
connectors={emailConnector ? [emailConnector] : []}
|
||||
type={ConnectorType.Email}
|
||||
onClickSetup={() => {
|
||||
setCreateType(ConnectorType.Email);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{socialConnectorGroups?.map(({ connectors, id }) => (
|
||||
<ConnectorRow key={id} connectors={connectors} type={ConnectorType.Social} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CreateForm
|
||||
|
|
|
@ -5,19 +5,7 @@
|
|||
}
|
||||
|
||||
.tableContainer {
|
||||
flex: 1;
|
||||
background-color: var(--color-layer-1);
|
||||
|
||||
> table {
|
||||
> tbody {
|
||||
> tr {
|
||||
> td {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
|
|
|
@ -87,71 +87,73 @@ const Users = () => {
|
|||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={classNames(styles.tableContainer, tableStyles.scrollable)}>
|
||||
<table className={classNames(!data && tableStyles.empty)}>
|
||||
<colgroup>
|
||||
<col className={styles.userName} />
|
||||
<col />
|
||||
<col />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t('users.user_name')}</th>
|
||||
<th>{t('users.application_name')}</th>
|
||||
<th>{t('users.latest_sign_in')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{!data && error && (
|
||||
<TableError
|
||||
columns={3}
|
||||
content={error.body?.message ?? error.message}
|
||||
onRetry={async () => mutate(undefined, true)}
|
||||
/>
|
||||
)}
|
||||
{isLoading && <TableLoading columns={3} />}
|
||||
{users?.length === 0 && (
|
||||
<TableEmpty columns={3}>
|
||||
<Button
|
||||
title="users.create"
|
||||
type="outline"
|
||||
onClick={() => {
|
||||
setIsCreateFormOpen(true);
|
||||
}}
|
||||
/>
|
||||
</TableEmpty>
|
||||
)}
|
||||
{users?.map(({ id, name, avatar, lastSignInAt, applicationId }) => (
|
||||
<tr
|
||||
key={id}
|
||||
className={tableStyles.clickable}
|
||||
onClick={() => {
|
||||
navigate(`/users/${id}`);
|
||||
}}
|
||||
>
|
||||
<td>
|
||||
<ItemPreview
|
||||
title={name ?? t('users.unnamed')}
|
||||
subtitle={id}
|
||||
icon={
|
||||
<img
|
||||
alt="avatar"
|
||||
className={styles.avatar}
|
||||
src={avatar ?? generateAvatarPlaceHolderById(id)}
|
||||
/>
|
||||
}
|
||||
to={`/users/${id}`}
|
||||
size="compact"
|
||||
/>
|
||||
</td>
|
||||
<td>{applicationId ? <ApplicationName applicationId={applicationId} /> : '-'}</td>
|
||||
<td>
|
||||
<DateTime>{lastSignInAt}</DateTime>
|
||||
</td>
|
||||
<div className={classNames(resourcesStyles.table, styles.tableContainer)}>
|
||||
<div className={tableStyles.scrollable}>
|
||||
<table className={classNames(!data && tableStyles.empty)}>
|
||||
<colgroup>
|
||||
<col className={styles.userName} />
|
||||
<col />
|
||||
<col />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t('users.user_name')}</th>
|
||||
<th>{t('users.application_name')}</th>
|
||||
<th>{t('users.latest_sign_in')}</th>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
{!data && error && (
|
||||
<TableError
|
||||
columns={3}
|
||||
content={error.body?.message ?? error.message}
|
||||
onRetry={async () => mutate(undefined, true)}
|
||||
/>
|
||||
)}
|
||||
{isLoading && <TableLoading columns={3} />}
|
||||
{users?.length === 0 && (
|
||||
<TableEmpty columns={3}>
|
||||
<Button
|
||||
title="users.create"
|
||||
type="outline"
|
||||
onClick={() => {
|
||||
setIsCreateFormOpen(true);
|
||||
}}
|
||||
/>
|
||||
</TableEmpty>
|
||||
)}
|
||||
{users?.map(({ id, name, avatar, lastSignInAt, applicationId }) => (
|
||||
<tr
|
||||
key={id}
|
||||
className={tableStyles.clickable}
|
||||
onClick={() => {
|
||||
navigate(`/users/${id}`);
|
||||
}}
|
||||
>
|
||||
<td>
|
||||
<ItemPreview
|
||||
title={name ?? t('users.unnamed')}
|
||||
subtitle={id}
|
||||
icon={
|
||||
<img
|
||||
alt="avatar"
|
||||
className={styles.avatar}
|
||||
src={avatar ?? generateAvatarPlaceHolderById(id)}
|
||||
/>
|
||||
}
|
||||
to={`/users/${id}`}
|
||||
size="compact"
|
||||
/>
|
||||
</td>
|
||||
<td>{applicationId ? <ApplicationName applicationId={applicationId} /> : '-'}</td>
|
||||
<td>
|
||||
<DateTime>{lastSignInAt}</DateTime>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<Pagination
|
||||
pageIndex={pageIndex}
|
||||
|
|
|
@ -2,17 +2,19 @@
|
|||
|
||||
.container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: _.unit(6);
|
||||
@include _.flex-column;
|
||||
|
||||
.headline {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.table {
|
||||
flex: 1;
|
||||
margin-top: _.unit(4);
|
||||
background-color: var(--color-layer-1);
|
||||
}
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.headline {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.table {
|
||||
flex: 1;
|
||||
margin-top: _.unit(4);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
|
|
@ -13,20 +13,28 @@ tr.clickable {
|
|||
}
|
||||
|
||||
.scrollable {
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--color-divider);
|
||||
max-height: 100%;
|
||||
background-color: var(--color-layer-1);
|
||||
border-radius: 12px;
|
||||
overflow-y: auto;
|
||||
|
||||
table {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
|
||||
thead tr {
|
||||
thead {
|
||||
background: var(--color-layer-1);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
th {
|
||||
background: var(--color-layer-1);
|
||||
tbody {
|
||||
tr {
|
||||
&:last-child {
|
||||
td {
|
||||
border-bottom: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue