forked from tsai/budibase
23 changed files with 951 additions and 252 deletions
@ -0,0 +1,215 @@ |
|||||
|
<script> |
||||
|
import "@spectrum-css/inputgroup/dist/index-vars.css" |
||||
|
import "@spectrum-css/popover/dist/index-vars.css" |
||||
|
import "@spectrum-css/menu/dist/index-vars.css" |
||||
|
import { fly } from "svelte/transition" |
||||
|
import { createEventDispatcher } from "svelte" |
||||
|
import clickOutside from "../../Actions/click_outside" |
||||
|
|
||||
|
export let inputValue |
||||
|
export let dropdownValue |
||||
|
export let id = null |
||||
|
export let inputType = "text" |
||||
|
export let placeholder = "Choose an option or type" |
||||
|
export let disabled = false |
||||
|
export let readonly = false |
||||
|
export let updateOnChange = true |
||||
|
export let error = null |
||||
|
export let options = [] |
||||
|
export let getOptionLabel = option => extractProperty(option, "label") |
||||
|
export let getOptionValue = option => extractProperty(option, "value") |
||||
|
export let isOptionSelected = () => false |
||||
|
|
||||
|
const dispatch = createEventDispatcher() |
||||
|
let open = false |
||||
|
let focus = false |
||||
|
|
||||
|
$: fieldText = getFieldText(dropdownValue, options, placeholder) |
||||
|
|
||||
|
const getFieldText = (dropdownValue, options, placeholder) => { |
||||
|
// Always use placeholder if no value |
||||
|
if (dropdownValue == null || dropdownValue === "") { |
||||
|
return placeholder || "Choose an option or type" |
||||
|
} |
||||
|
|
||||
|
// Wait for options to load if there is a value but no options |
||||
|
if (!options?.length) { |
||||
|
return "" |
||||
|
} |
||||
|
|
||||
|
// Render the label if the selected option is found, otherwise raw value |
||||
|
const selected = options.find( |
||||
|
option => getOptionValue(option) === dropdownValue |
||||
|
) |
||||
|
return selected ? getOptionLabel(selected) : dropdownValue |
||||
|
} |
||||
|
|
||||
|
const updateValue = newValue => { |
||||
|
if (readonly) { |
||||
|
return |
||||
|
} |
||||
|
dispatch("change", newValue) |
||||
|
} |
||||
|
|
||||
|
const onFocus = () => { |
||||
|
if (readonly) { |
||||
|
return |
||||
|
} |
||||
|
focus = true |
||||
|
} |
||||
|
|
||||
|
const onBlur = event => { |
||||
|
if (readonly) { |
||||
|
return |
||||
|
} |
||||
|
focus = false |
||||
|
updateValue(event.target.value) |
||||
|
} |
||||
|
|
||||
|
const onInput = event => { |
||||
|
if (readonly || !updateOnChange) { |
||||
|
return |
||||
|
} |
||||
|
updateValue(event.target.value) |
||||
|
} |
||||
|
|
||||
|
const updateValueOnEnter = event => { |
||||
|
if (readonly) { |
||||
|
return |
||||
|
} |
||||
|
if (event.key === "Enter") { |
||||
|
updateValue(event.target.value) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const onClick = () => { |
||||
|
dispatch("click") |
||||
|
if (readonly) { |
||||
|
return |
||||
|
} |
||||
|
open = true |
||||
|
} |
||||
|
|
||||
|
const onPick = newValue => { |
||||
|
dispatch("pick", newValue) |
||||
|
open = false |
||||
|
} |
||||
|
|
||||
|
const extractProperty = (value, property) => { |
||||
|
if (value && typeof value === "object") { |
||||
|
return value[property] |
||||
|
} |
||||
|
return value |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<div |
||||
|
class="spectrum-InputGroup" |
||||
|
class:is-invalid={!!error} |
||||
|
class:is-disabled={disabled} |
||||
|
> |
||||
|
<div |
||||
|
class="spectrum-Textfield spectrum-InputGroup-textfield" |
||||
|
class:is-invalid={!!error} |
||||
|
class:is-disabled={disabled} |
||||
|
class:is-focused={focus} |
||||
|
> |
||||
|
<input |
||||
|
{id} |
||||
|
on:click |
||||
|
on:blur |
||||
|
on:focus |
||||
|
on:input |
||||
|
on:keyup |
||||
|
on:blur={onBlur} |
||||
|
on:focus={onFocus} |
||||
|
on:input={onInput} |
||||
|
on:keyup={updateValueOnEnter} |
||||
|
value={inputValue || ""} |
||||
|
placeholder={placeholder || ""} |
||||
|
{disabled} |
||||
|
{readonly} |
||||
|
{inputType} |
||||
|
class="spectrum-Textfield-input spectrum-InputGroup-input" |
||||
|
/> |
||||
|
</div> |
||||
|
<div style="width: 30%"> |
||||
|
<button |
||||
|
{id} |
||||
|
class="spectrum-Picker spectrum-Picker--sizeM override-borders" |
||||
|
{disabled} |
||||
|
class:is-open={open} |
||||
|
aria-haspopup="listbox" |
||||
|
on:mousedown={onClick} |
||||
|
> |
||||
|
<span class="spectrum-Picker-label"> |
||||
|
{fieldText} |
||||
|
</span> |
||||
|
<svg |
||||
|
class="spectrum-Icon spectrum-UIIcon-ChevronDown100 spectrum-Picker-menuIcon" |
||||
|
focusable="false" |
||||
|
aria-hidden="true" |
||||
|
> |
||||
|
<use xlink:href="#spectrum-css-icon-Chevron100" /> |
||||
|
</svg> |
||||
|
</button> |
||||
|
{#if open} |
||||
|
<div |
||||
|
use:clickOutside={() => (open = false)} |
||||
|
transition:fly|local={{ y: -20, duration: 200 }} |
||||
|
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open" |
||||
|
> |
||||
|
<ul class="spectrum-Menu" role="listbox"> |
||||
|
{#each options as option, idx} |
||||
|
<li |
||||
|
class="spectrum-Menu-item" |
||||
|
class:is-selected={isOptionSelected(getOptionValue(option, idx))} |
||||
|
role="option" |
||||
|
aria-selected="true" |
||||
|
tabindex="0" |
||||
|
on:click={() => onPick(getOptionValue(option, idx))} |
||||
|
> |
||||
|
<span class="spectrum-Menu-itemLabel"> |
||||
|
{getOptionLabel(option, idx)} |
||||
|
</span> |
||||
|
<svg |
||||
|
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon" |
||||
|
focusable="false" |
||||
|
aria-hidden="true" |
||||
|
> |
||||
|
<use xlink:href="#spectrum-css-icon-Checkmark100" /> |
||||
|
</svg> |
||||
|
</li> |
||||
|
{/each} |
||||
|
</ul> |
||||
|
</div> |
||||
|
{/if} |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.spectrum-InputGroup { |
||||
|
min-width: 0; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.spectrum-InputGroup-input { |
||||
|
border-right-width: 1px; |
||||
|
} |
||||
|
.spectrum-Textfield { |
||||
|
width: 100%; |
||||
|
} |
||||
|
.spectrum-Textfield-input { |
||||
|
width: 0; |
||||
|
} |
||||
|
|
||||
|
.override-borders { |
||||
|
border-top-left-radius: 0px; |
||||
|
border-bottom-left-radius: 0px; |
||||
|
} |
||||
|
.spectrum-Popover { |
||||
|
max-height: 240px; |
||||
|
z-index: 999; |
||||
|
top: 100%; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,55 @@ |
|||||
|
<script> |
||||
|
import Field from "./Field.svelte" |
||||
|
import InputDropdown from "./Core/InputDropdown.svelte" |
||||
|
import { createEventDispatcher } from "svelte" |
||||
|
|
||||
|
export let inputValue = null |
||||
|
export let dropdownValue = null |
||||
|
export let inputType = "text" |
||||
|
export let label = null |
||||
|
export let labelPosition = "above" |
||||
|
export let placeholder = null |
||||
|
export let disabled = false |
||||
|
export let readonly = false |
||||
|
export let error = null |
||||
|
export let updateOnChange = true |
||||
|
export let quiet = false |
||||
|
export let dataCy |
||||
|
export let autofocus |
||||
|
export let options = [] |
||||
|
|
||||
|
const dispatch = createEventDispatcher() |
||||
|
|
||||
|
const onPick = e => { |
||||
|
dropdownValue = e.detail |
||||
|
dispatch("pick", e.detail) |
||||
|
} |
||||
|
const onChange = e => { |
||||
|
inputValue = e.detail |
||||
|
dispatch("change", e.detail) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<Field {label} {labelPosition} {error}> |
||||
|
<InputDropdown |
||||
|
{dataCy} |
||||
|
{updateOnChange} |
||||
|
{error} |
||||
|
{disabled} |
||||
|
{readonly} |
||||
|
{inputValue} |
||||
|
{dropdownValue} |
||||
|
{placeholder} |
||||
|
{inputType} |
||||
|
{quiet} |
||||
|
{autofocus} |
||||
|
{options} |
||||
|
on:change={onChange} |
||||
|
on:pick={onPick} |
||||
|
on:click |
||||
|
on:input |
||||
|
on:blur |
||||
|
on:focus |
||||
|
on:keyup |
||||
|
/> |
||||
|
</Field> |
||||
@ -0,0 +1,73 @@ |
|||||
|
<script> |
||||
|
import { ActionButton, Icon, Search, Divider, Detail } from "@budibase/bbui" |
||||
|
|
||||
|
export let searchTerm = "" |
||||
|
export let selected |
||||
|
export let filtered |
||||
|
export let addAll |
||||
|
export let select |
||||
|
export let title |
||||
|
export let key |
||||
|
</script> |
||||
|
|
||||
|
<div style="padding: var(--spacing-m)"> |
||||
|
<Search placeholder="Search" bind:value={searchTerm} /> |
||||
|
<div class="header sub-header"> |
||||
|
<div> |
||||
|
<Detail |
||||
|
>{filtered.length} {title}{filtered.length === 1 ? "" : "s"}</Detail |
||||
|
> |
||||
|
</div> |
||||
|
<div> |
||||
|
<ActionButton on:click={addAll} emphasized size="S">Add all</ActionButton> |
||||
|
</div> |
||||
|
</div> |
||||
|
<Divider noMargin /> |
||||
|
<div> |
||||
|
{#each filtered as item} |
||||
|
<div |
||||
|
on:click={select(item._id)} |
||||
|
style="padding-bottom: var(--spacing-m)" |
||||
|
class="selection" |
||||
|
> |
||||
|
<div> |
||||
|
{item[key]} |
||||
|
</div> |
||||
|
|
||||
|
{#if selected.includes(item._id)} |
||||
|
<div> |
||||
|
<Icon |
||||
|
color="var(--spectrum-global-color-blue-600);" |
||||
|
name="Checkmark" |
||||
|
/> |
||||
|
</div> |
||||
|
{/if} |
||||
|
</div> |
||||
|
{/each} |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.header { |
||||
|
align-items: center; |
||||
|
padding: var(--spacing-m) 0 var(--spacing-m) 0; |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
} |
||||
|
|
||||
|
.selection { |
||||
|
align-items: end; |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
.selection > :first-child { |
||||
|
padding-top: var(--spacing-m); |
||||
|
} |
||||
|
|
||||
|
.sub-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
} |
||||
|
</style> |
||||
@ -1,82 +1,78 @@ |
|||||
<script> |
<script> |
||||
import { |
import { |
||||
Body, |
ActionButton, |
||||
Input, |
Layout, |
||||
Select, |
Label, |
||||
ModalContent, |
ModalContent, |
||||
|
Multiselect, |
||||
notifications, |
notifications, |
||||
Toggle, |
InputDropdown, |
||||
Label, |
|
||||
} from "@budibase/bbui" |
} from "@budibase/bbui" |
||||
import { createValidationStore, emailValidator } from "helpers/validation" |
import { users, groups } from "stores/portal" |
||||
import { users } from "stores/portal" |
|
||||
import analytics, { Events } from "analytics" |
import analytics, { Events } from "analytics" |
||||
|
import { Constants } from "@budibase/frontend-core" |
||||
|
|
||||
export let disabled |
export let disabled |
||||
|
export let showOnboardingTypeModal |
||||
const options = ["Email onboarding", "Basic onboarding"] |
const options = ["Email onboarding", "Basic onboarding"] |
||||
let selected = options[0] |
let selected = options[0] |
||||
let builder, admin |
let builder, admin |
||||
|
|
||||
const [email, error, touched] = createValidationStore("", emailValidator) |
$: userData = [{ email: "", role: "", error: null }] |
||||
|
|
||||
|
/* |
||||
async function createUserFlow() { |
async function createUserFlow() { |
||||
try { |
try { |
||||
const res = await users.invite({ email: $email, builder, admin }) |
const res = await users.invite({ email: "", builder, admin }) |
||||
notifications.success(res.message) |
notifications.success(res.message) |
||||
analytics.captureEvent(Events.USER.INVITE, { type: selected }) |
analytics.captureEvent(Events.USER.INVITE, { type: selected }) |
||||
} catch (error) { |
} catch (error) { |
||||
notifications.error("Error inviting user") |
notifications.error("Error inviting user") |
||||
} |
} |
||||
} |
} |
||||
|
*/ |
||||
|
function addNewInput() { |
||||
|
userData = [...userData, { email: "", role: "" }] |
||||
|
} |
||||
</script> |
</script> |
||||
|
|
||||
<ModalContent |
<ModalContent |
||||
onConfirm={createUserFlow} |
onConfirm={showOnboardingTypeModal} |
||||
size="M" |
size="M" |
||||
title="Add new user" |
title="Add new user" |
||||
confirmText="Add user" |
confirmText="Add user" |
||||
confirmDisabled={disabled} |
confirmDisabled={disabled} |
||||
cancelText="Cancel" |
cancelText="Cancel" |
||||
disabled={$error} |
|
||||
showCloseIcon={false} |
showCloseIcon={false} |
||||
> |
> |
||||
<Body size="S"> |
<Layout noPadding gap="XS"> |
||||
If you have SMTP configured and an email for the new user, you can use the |
<Label>Email Address</Label> |
||||
automated email onboarding flow. Otherwise, use our basic onboarding process |
|
||||
with autogenerated passwords. |
{#each userData as input, index} |
||||
</Body> |
<InputDropdown |
||||
<Select |
inputType="email" |
||||
placeholder={null} |
bind:inputValue={input.email} |
||||
bind:value={selected} |
bind:dropdownValue={input.role} |
||||
on:change |
options={Constants.BbRoles} |
||||
{options} |
error={input.error} |
||||
label="Add new user via:" |
/> |
||||
/> |
{/each} |
||||
<Input |
<div> |
||||
type="email" |
<ActionButton on:click={addNewInput} icon="Add">Add email</ActionButton> |
||||
bind:value={$email} |
|
||||
error={$touched && $error} |
|
||||
placeholder="john@doe.com" |
|
||||
label="Email" |
|
||||
/> |
|
||||
<div> |
|
||||
<div class="toggle"> |
|
||||
<Label size="L">Development access</Label> |
|
||||
<Toggle text="" bind:value={builder} /> |
|
||||
</div> |
|
||||
<div class="toggle"> |
|
||||
<Label size="L">Administration access</Label> |
|
||||
<Toggle text="" bind:value={admin} /> |
|
||||
</div> |
</div> |
||||
</div> |
</Layout> |
||||
|
|
||||
|
<Multiselect |
||||
|
placeholder="Select User Groups" |
||||
|
label="User Groups" |
||||
|
options={$groups} |
||||
|
getOptionLabel={option => option.name} |
||||
|
getOptionValue={option => option.name} |
||||
|
/> |
||||
</ModalContent> |
</ModalContent> |
||||
|
|
||||
<style> |
<style> |
||||
.toggle { |
:global(.spectrum-Picker) { |
||||
display: grid; |
border-top-left-radius: 0px; |
||||
grid-template-columns: 78% 1fr; |
|
||||
align-items: center; |
|
||||
width: 50%; |
|
||||
} |
} |
||||
</style> |
</style> |
||||
|
|||||
@ -0,0 +1,52 @@ |
|||||
|
<script> |
||||
|
import { Body, ModalContent, RadioGroup, Multiselect } from "@budibase/bbui" |
||||
|
import { groups } from "stores/portal" |
||||
|
|
||||
|
import { Constants } from "@budibase/frontend-core" |
||||
|
</script> |
||||
|
|
||||
|
<ModalContent |
||||
|
size="M" |
||||
|
title="Import users" |
||||
|
confirmText="Done" |
||||
|
showCancelButton={false} |
||||
|
cancelText="Cancel" |
||||
|
showCloseIcon={false} |
||||
|
> |
||||
|
<Body size="S">Import your users email addrresses from a CSV</Body> |
||||
|
|
||||
|
<div class="container"> |
||||
|
<div class="inner"> |
||||
|
<Body size="S">Upload</Body> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<RadioGroup options={Constants.BuilderRoleDescriptions} /> |
||||
|
|
||||
|
<Multiselect |
||||
|
placeholder="Select User Groups" |
||||
|
label="User Groups" |
||||
|
options={$groups} |
||||
|
getOptionLabel={option => option.name} |
||||
|
getOptionValue={option => option.name} |
||||
|
/> |
||||
|
</ModalContent> |
||||
|
|
||||
|
<style> |
||||
|
.inner { |
||||
|
display: flex; |
||||
|
} |
||||
|
|
||||
|
:global(.spectrum-Picker) { |
||||
|
border-top-left-radius: 0px; |
||||
|
} |
||||
|
|
||||
|
.container { |
||||
|
width: 100%; |
||||
|
height: var(--spectrum-alias-item-height-l); |
||||
|
background: var(--spectrum-global-color-gray-200); |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,107 @@ |
|||||
|
<script> |
||||
|
import { ModalContent, Body, Layout, Icon } from "@budibase/bbui" |
||||
|
export let showConfirmationModal |
||||
|
let emailOnboardingKey = "emailOnboarding" |
||||
|
let basicOnboaridngKey = "basicOnboarding" |
||||
|
|
||||
|
let selectedOnboardingType |
||||
|
</script> |
||||
|
|
||||
|
<ModalContent |
||||
|
size="M" |
||||
|
title="Choose your onboarding" |
||||
|
confirmText="Done" |
||||
|
cancelText="Cancel" |
||||
|
showCloseIcon={false} |
||||
|
onConfirm={() => showConfirmationModal(selectedOnboardingType)} |
||||
|
disabled={!selectedOnboardingType} |
||||
|
> |
||||
|
<Layout noPadding gap="S"> |
||||
|
<div |
||||
|
class="onboarding-type item" |
||||
|
class:selected={selectedOnboardingType == emailOnboardingKey} |
||||
|
on:click={() => { |
||||
|
selectedOnboardingType = emailOnboardingKey |
||||
|
}} |
||||
|
> |
||||
|
<div class="content onboarding-type-wrap"> |
||||
|
<Icon name="WebPage" /> |
||||
|
<div class="onboarding-type-text"> |
||||
|
<Body size="S">Send email invites</Body> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div style="color: var(--spectrum-global-color-green-600); float: right"> |
||||
|
{#if selectedOnboardingType == emailOnboardingKey} |
||||
|
<div class="checkmark-spacing"> |
||||
|
<Icon size="S" name="CheckmarkCircle" /> |
||||
|
</div> |
||||
|
{/if} |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div |
||||
|
class="onboarding-type item" |
||||
|
class:selected={selectedOnboardingType == basicOnboaridngKey} |
||||
|
on:click={() => { |
||||
|
selectedOnboardingType = basicOnboaridngKey |
||||
|
}} |
||||
|
> |
||||
|
<div class="content onboarding-type-wrap"> |
||||
|
<Icon name="Key" /> |
||||
|
<div class="onboarding-type-text"> |
||||
|
<Body size="S">Generate passwords for each user</Body> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div style="color: var(--spectrum-global-color-green-600); float: right"> |
||||
|
{#if selectedOnboardingType == basicOnboaridngKey} |
||||
|
<div class="checkmark-spacing"> |
||||
|
<Icon size="S" name="CheckmarkCircle" /> |
||||
|
</div> |
||||
|
{/if} |
||||
|
</div> |
||||
|
</div> |
||||
|
</Layout> |
||||
|
</ModalContent> |
||||
|
|
||||
|
<style> |
||||
|
.onboarding-type.item { |
||||
|
padding: var(--spectrum-alias-item-padding-xl); |
||||
|
} |
||||
|
.onboarding-type-wrap { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
align-items: center; |
||||
|
} |
||||
|
.checkmark-spacing { |
||||
|
margin-right: var(--spacing-m); |
||||
|
} |
||||
|
.content { |
||||
|
letter-spacing: 0px; |
||||
|
} |
||||
|
.item { |
||||
|
cursor: pointer; |
||||
|
grid-gap: var(--spectrum-alias-grid-margin-xsmall); |
||||
|
padding: var(--spectrum-alias-item-padding-s); |
||||
|
background: var(--spectrum-alias-background-color-primary); |
||||
|
transition: 0.3s all; |
||||
|
border: 1px solid var(--spectrum-global-color-gray-300); |
||||
|
border-radius: 4px; |
||||
|
border-width: 1px; |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
} |
||||
|
.item:hover, |
||||
|
.selected { |
||||
|
background: var(--spectrum-alias-background-color-tertiary); |
||||
|
} |
||||
|
.onboarding-type-wrap .onboarding-type-text { |
||||
|
padding-left: var(--spectrum-alias-item-padding-xl); |
||||
|
} |
||||
|
.onboarding-type-wrap :global(.spectrum-Icon) { |
||||
|
min-width: var(--spectrum-icon-size-m); |
||||
|
} |
||||
|
.onboarding-type-wrap :global(.spectrum-Heading) { |
||||
|
padding-bottom: var(--spectrum-alias-item-padding-s); |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,15 @@ |
|||||
|
<script> |
||||
|
import { InternalRenderer } from "@budibase/bbui" |
||||
|
|
||||
|
export let value |
||||
|
</script> |
||||
|
|
||||
|
<div style="display: flex; "> |
||||
|
{value} |
||||
|
<div style="margin-left: 1.5rem;"> |
||||
|
<InternalRenderer {value} /> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
</style> |
||||
@ -0,0 +1,61 @@ |
|||||
|
<script> |
||||
|
import { Body, ModalContent, Table, Icon } from "@budibase/bbui" |
||||
|
import PasswordCopyRenderer from "./PasswordCopyRenderer.svelte" |
||||
|
|
||||
|
const schema = { |
||||
|
email: {}, |
||||
|
password: {}, |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<ModalContent |
||||
|
size="S" |
||||
|
title="Accounts created!" |
||||
|
confirmText="Done" |
||||
|
showCancelButton={false} |
||||
|
cancelText="Cancel" |
||||
|
showCloseIcon={false} |
||||
|
> |
||||
|
<Body size="XS" |
||||
|
>All your new users can be accessed through the autogenerated passwords. |
||||
|
Make not of these passwords or download the csv</Body |
||||
|
> |
||||
|
|
||||
|
<div class="container"> |
||||
|
<div class="inner"> |
||||
|
<Icon name="Download" /> |
||||
|
|
||||
|
<div style="margin-left: var(--spacing-m)"> |
||||
|
<Body size="XS">Passwords CSV</Body> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<Table |
||||
|
{schema} |
||||
|
data={[{ email: "test", password: "§xz§§zvzxvxzv" }]} |
||||
|
allowEditColumns={false} |
||||
|
allowEditRows={false} |
||||
|
allowSelectRows={false} |
||||
|
customRenderers={[{ column: "password", component: PasswordCopyRenderer }]} |
||||
|
/> |
||||
|
</ModalContent> |
||||
|
|
||||
|
<style> |
||||
|
.inner { |
||||
|
display: flex; |
||||
|
} |
||||
|
|
||||
|
:global(.spectrum-Picker) { |
||||
|
border-top-left-radius: 0px; |
||||
|
} |
||||
|
|
||||
|
.container { |
||||
|
width: 100%; |
||||
|
height: var(--spectrum-alias-item-height-l); |
||||
|
background: #009562; |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
} |
||||
|
</style> |
||||
Loading…
Reference in new issue