Skip to content

Commit

Permalink
Merge pull request #148 from IQSS/feature/136-files-table-checkboxes-…
Browse files Browse the repository at this point in the history
…selection-ui

136 - Files table UI [2/2] - Row selection
  • Loading branch information
kcondon authored Aug 14, 2023
2 parents 82b8ec1 + c7cbfbe commit b44205d
Show file tree
Hide file tree
Showing 65 changed files with 1,099 additions and 181 deletions.
10 changes: 5 additions & 5 deletions packages/design-system/src/lib/assets/styles/fontcustom.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@
font-weight: normal;
font-family: fontcustom;
font-style: normal;
src: url("/fontcustom/fontcustom_8c9f858763dbca8e064e7fd59d1aa2b7.woff2") format("woff2"),
url("/fontcustom/fontcustom_8c9f858763dbca8e064e7fd59d1aa2b7.woff") format("woff"),
url("/fontcustom/fontcustom_8c9f858763dbca8e064e7fd59d1aa2b7.ttf") format("truetype"),
url("/fontcustom/fontcustom_8c9f858763dbca8e064e7fd59d1aa2b7.svg#fontcustom") format("svg");
src: url("fontcustom/fontcustom_8c9f858763dbca8e064e7fd59d1aa2b7.woff2") format("woff2"),
url("fontcustom/fontcustom_8c9f858763dbca8e064e7fd59d1aa2b7.woff") format("woff"),
url("fontcustom/fontcustom_8c9f858763dbca8e064e7fd59d1aa2b7.ttf") format("truetype"),
url("fontcustom/fontcustom_8c9f858763dbca8e064e7fd59d1aa2b7.svg#fontcustom") format("svg");
}

@media screen and (min-resolution) {
@font-face {
font-family: fontcustom;
src: url("/fontcustom/fontcustom_8c9f858763dbca8e064e7fd59d1aa2b7.svg#fontcustom") format("svg");
src: url("fontcustom/fontcustom_8c9f858763dbca8e064e7fd59d1aa2b7.svg#fontcustom") format("svg");
}
}

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Binary file not shown.
12 changes: 7 additions & 5 deletions packages/design-system/src/lib/components/button/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { MouseEvent, ReactNode } from 'react'
import { HTMLAttributes, MouseEvent, ReactNode } from 'react'
import styles from './Button.module.scss'
import { Button as ButtonBS } from 'react-bootstrap'
import { IconName } from '../icon/IconName'
import { Icon } from '../icon/Icon'

type ButtonVariant = 'primary' | 'secondary' | 'link'

interface ButtonProps {
interface ButtonProps extends HTMLAttributes<HTMLButtonElement> {
variant?: ButtonVariant
disabled?: boolean
onClick?: (event: MouseEvent<HTMLButtonElement>) => void
icon?: IconName | ReactNode
withSpacing?: boolean
children: ReactNode
children?: ReactNode
}

export function Button({
Expand All @@ -21,15 +21,17 @@ export function Button({
onClick,
icon,
withSpacing,
children
children,
...props
}: ButtonProps) {
return (
<ButtonBS
className={withSpacing ? styles.spacing : ''}
variant={variant}
onClick={disabled ? undefined : onClick}
disabled={disabled}
aria-disabled={disabled}>
aria-disabled={disabled}
{...props}>
{typeof icon === 'string' ? <Icon name={icon} /> : icon}
{children}
</ButtonBS>
Expand Down
2 changes: 2 additions & 0 deletions packages/design-system/src/lib/components/form/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { FormEvent, PropsWithChildren } from 'react'
import { FormGroup } from './form-group/FormGroup'
import { Form as FormBS } from 'react-bootstrap'
import { FormGroupWithMultipleFields } from './form-group-multiple-fields/FormGroupWithMultipleFields'
import { FormInputGroup } from './form-group/form-input-group/FormInputGroup'

interface FormProps {
validated?: boolean
Expand All @@ -16,6 +17,7 @@ function Form({ validated, onSubmit, children }: PropsWithChildren<FormProps>) {
)
}

Form.InputGroup = FormInputGroup
Form.Group = FormGroup
Form.GroupWithMultipleFields = FormGroupWithMultipleFields

Expand Down
Original file line number Diff line number Diff line change
@@ -1,40 +1,24 @@
import { Form as FormBS, InputGroup } from 'react-bootstrap'
import { Form as FormBS } from 'react-bootstrap'
import { FormElementLayout } from './FormElementLayout'
import { PropsWithChildren } from 'react'
import * as React from 'react'

export type FormInputElement = HTMLInputElement | HTMLTextAreaElement

interface FormInputProps extends React.HTMLAttributes<FormInputElement> {
type?: 'text' | 'email' | 'password'
readOnly?: boolean
prefix?: string
withinMultipleFieldsGroup?: boolean
}

export function FormInput({
type = 'text',
readOnly,
prefix,
withinMultipleFieldsGroup,
...props
}: FormInputProps) {
const FormInputPrefix = ({ children }: PropsWithChildren) => {
return prefix ? (
<InputGroup className="mb-3">
<InputGroup.Text>{prefix}</InputGroup.Text>
{children}
</InputGroup>
) : (
<>{children}</>
)
}

return (
<FormElementLayout withinMultipleFieldsGroup={withinMultipleFieldsGroup}>
<FormInputPrefix>
<FormBS.Control type={type} readOnly={readOnly} plaintext={readOnly} {...props} />
</FormInputPrefix>
<FormBS.Control type={type} readOnly={readOnly} plaintext={readOnly} {...props} />
</FormElementLayout>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React, { PropsWithChildren } from 'react'
import { InputGroup } from 'react-bootstrap'
import { FormInputGroupText } from './FormInputGroupText'
import { Col } from '../../../grid/Col'

interface FormInputGroupProps {
hasVisibleLabel?: boolean
}

function FormInputGroup({ hasVisibleLabel, children }: PropsWithChildren<FormInputGroupProps>) {
const childrenInsideGroup = React.Children.map(children as JSX.Element, (child) => {
return React.cloneElement(child, {
withinMultipleFieldsGroup: true
})
})

return hasVisibleLabel ? (
<Col sm={9}>
<InputGroup className="mb-3">{childrenInsideGroup}</InputGroup>
</Col>
) : (
<InputGroup className="mb-3">{childrenInsideGroup}</InputGroup>
)
}

FormInputGroup.Text = FormInputGroupText

export { FormInputGroup }
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { PropsWithChildren } from 'react'
import { InputGroup } from 'react-bootstrap'

export function FormInputGroupText({ children }: PropsWithChildren) {
return <InputGroup.Text>{children}</InputGroup.Text>
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Meta, StoryObj } from '@storybook/react'
import { Button } from '../../components/button/Button'
import { IconName } from '../../components/icon/IconName'
import { Search } from 'react-bootstrap-icons'

/**
* ## Description
Expand Down Expand Up @@ -123,6 +124,10 @@ export const WithIcon: Story = {
render: () => <Button icon={IconName.COLLECTION}>Primary</Button>
}

export const IconOnly: Story = {
render: () => <Button icon={<Search />} variant="secondary" aria-label="Search" />
}

export const UseCasePrimaryButtonAsCallToAction: Story = {
name: 'Example use case: Primary button as call-to-action',
render: () => (
Expand Down
20 changes: 19 additions & 1 deletion packages/design-system/src/lib/stories/form/Form.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { Meta, StoryObj } from '@storybook/react'
import { Form } from '../../components/form/Form'
import { Col } from '../../components/grid/Col'
import { Row } from '../../components/grid/Row'
import { Button } from '../../components/button/Button'
import { Search } from 'react-bootstrap-icons'

/**
* ## Description
Expand Down Expand Up @@ -116,7 +118,23 @@ export const InputWithPrefix: Story = {
<Form>
<Form.Group controlId="basic-form-identifier">
<Form.Group.Label>Identifier</Form.Group.Label>
<Form.Group.Input type="text" placeholder="Identifier" prefix="https://dataverse.org/" />
<Form.InputGroup hasVisibleLabel>
<Form.InputGroup.Text>https://dataverse.org/</Form.InputGroup.Text>
<Form.Group.Input type="text" placeholder="Identifier" aria-label="identifier" />
</Form.InputGroup>
</Form.Group>
</Form>
)
}

export const InputWithButton: Story = {
render: () => (
<Form>
<Form.Group controlId="basic-form-search">
<Form.InputGroup>
<Form.Group.Input type="text" placeholder="Search..." aria-label="Search" />
<Button variant="secondary" icon={<Search />} aria-label="Search submit" />
</Form.InputGroup>
</Form.Group>
</Form>
)
Expand Down
5 changes: 5 additions & 0 deletions packages/design-system/tests/component/button/Button.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,9 @@ describe('Button', () => {
cy.findByText(clickMeText).click({ force: true })
cy.wrap(onClick).should('not.be.called')
})

it('renders a button with only an icon', () => {
cy.mount(<Button icon={IconName.COLLECTION} />)
cy.findByRole('img', { name: IconName.COLLECTION }).should('exist')
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,6 @@ describe('FormInput', () => {
cy.findByLabelText('Username').should('have.attr', 'readOnly')
})

it('should render with the specified prefix', () => {
cy.mount(
<FormGroup controlId="username">
<FormGroup.Label>Username</FormGroup.Label>
<FormGroup.Input prefix="Prefix:" type="text" readOnly />
</FormGroup>
)

cy.findByText('Prefix:').should('exist')
})

it('should render with the required symbol', () => {
cy.mount(
<FormGroup controlId="username" required>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { FormInputGroup } from '../../../../../src/lib/components/form/form-group/form-input-group/FormInputGroup'
import { FormGroup } from '../../../../../src/lib/components/form/form-group/FormGroup'
import { Search } from 'react-bootstrap-icons'
import { Button } from '../../../../../src/lib/components/button/Button'

describe('FormInputGroup', () => {
it('should render with input and text', () => {
cy.mount(
<FormGroup controlId="username">
<FormInputGroup>
<FormInputGroup.Text>https://dataverse.org/</FormInputGroup.Text>
<FormGroup.Input type="text" placeholder="Identifier" aria-label="identifier" />
</FormInputGroup>
</FormGroup>
)

cy.findByText('https://dataverse.org/').should('exist')
cy.findByLabelText('identifier').should('exist')
})

it('should render with input and button', () => {
cy.mount(
<FormGroup controlId="username">
<FormInputGroup>
<FormGroup.Input type="text" placeholder="Search..." aria-label="Search" />
<Button variant="secondary" icon={<Search />} />
</FormInputGroup>
</FormGroup>
)

cy.findByLabelText('Search').should('exist')
cy.findByRole('button').should('exist')
})
})
16 changes: 14 additions & 2 deletions public/locales/en/files.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
{
"filesLoading": "Files loading spinner symbol",
"table": {
"rowSelection": {
"filesSelected_one": "{{count}} file is currently selected.",
"filesSelected_other": "{{count}} files are currently selected.",
"selectAll": "Select all {{count}} files in this dataset.",
"clearSelection": "Clear selection."
},
"zipDownloadExceedsLimit": "The overall size of the files selected ({{selectionTotalSize}}) for download exceeds the zip limit of {{zipDownloadSizeLimit}}. Please unselect some files to continue.",
"pagination": {
"pageSize": "Files per page"
},
Expand Down Expand Up @@ -54,10 +61,10 @@
"title": "Filter by"
},
"filterByType": {
"title": "Filter Type"
"title": "File Type"
},
"filterByTag": {
"title": "Filter Tag"
"title": "File Tags"
},
"filterByAccess": {
"title": "Access",
Expand All @@ -68,6 +75,11 @@
"embargoed_public": "Embargoed then Public",
"embargoed_restricted": "Embargoed then Restricted"
}
},
"searchText": {
"placeholder": "Search this dataset...",
"label": "Search",
"submit": "Submit search"
}
}
}
13 changes: 13 additions & 0 deletions src/files/domain/models/File.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@ export class FileSize {
toString(): string {
return `${this.value} ${this.unit}`
}

toBytes(): number {
const multiplier = {
[FileSizeUnit.BYTES]: 1,
[FileSizeUnit.KILOBYTES]: 1024,
[FileSizeUnit.MEGABYTES]: 1024 ** 2,
[FileSizeUnit.GIGABYTES]: 1024 ** 3,
[FileSizeUnit.TERABYTES]: 1024 ** 4,
[FileSizeUnit.PETABYTES]: 1024 ** 5
}

return this.value * multiplier[this.unit]
}
}

export interface FileAccess {
Expand Down
13 changes: 12 additions & 1 deletion src/files/domain/models/FileCriteria.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ export class FileCriteria {
public readonly sortBy: FileSortByOption = FileSortByOption.NAME_AZ,
public readonly filterByType?: FileType,
public readonly filterByAccess?: FileAccessOption,
public readonly filterByTag?: FileTag
public readonly filterByTag?: FileTag,
public readonly searchText?: string
) {}

withSortBy(sortBy: FileSortByOption): FileCriteria {
Expand All @@ -27,6 +28,16 @@ export class FileCriteria {

return new FileCriteria(this.sortBy, this.filterByType, this.filterByAccess, newFilterByTag)
}

withSearchText(searchText: string | undefined): FileCriteria {
return new FileCriteria(
this.sortBy,
this.filterByType,
this.filterByAccess,
this.filterByTag,
searchText
)
}
}

export enum FileSortByOption {
Expand Down
3 changes: 2 additions & 1 deletion src/files/infrastructure/FileJSDataverseRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ export class FileJSDataverseRepository implements FileRepository {
}, 1000)
})
}
// eslint-disable-next-line unused-imports/no-unused-vars
getCountInfoByDatasetPersistentId(
// eslint-disable-next-line unused-imports/no-unused-vars
persistentId: string,
// eslint-disable-next-line unused-imports/no-unused-vars
version?: string
): Promise<FilesCountInfo> {
// TODO - implement using js-dataverse
Expand Down
Loading

0 comments on commit b44205d

Please sign in to comment.