Skip to content

Commit

Permalink
feat: migrate all the code from catala-dsfr
Browse files Browse the repository at this point in the history
  • Loading branch information
EmileRolley committed Jun 20, 2024
1 parent 3963102 commit 0107ebc
Show file tree
Hide file tree
Showing 13 changed files with 1,738 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .prettierrc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
semi: false
singleQuote: true
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# rjsf-dsfr

react-jsonschema-form for the DSFR
63 changes: 63 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"name": "@codegouvfr/rjsf-dsfr",
"version": "0.1.0",
"description": "react-jsonschema-form for the DSFR",
"repository": "[email protected]:codegouvfr/rjsf-dsfr.git",
"author": "Emile Rolley <[email protected]>",
"keywords": [
"DSFR",
"react-jsonschema-form",
"react",
"jsonschema",
"form",
"form-builder"
],
"license": "MIT",
"type": "module",
"main": "dist/index.cjs",
"module": "dist/index.js",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"format:check": "prettier --check .",
"format": "prettier --write ."
},
"devDependencies": {
"@types/react": "^18.3.3",
"prettier": "^3.3.2",
"tsup": "^8.1.0",
"typescript": "^5.4.5"
},
"tsup": {
"entry": [
"./src/index.tsx"
],
"format": [
"cjs",
"esm"
],
"sourcemap": true,
"clean": true,
"dts": true
},
"dependencies": {
"@codegouvfr/react-dsfr": "^1.9.20",
"@rjsf/core": "^5.18.4",
"@rjsf/utils": "^5.18.4",
"@rjsf/validator-ajv8": "^5.18.4",
"react": "^18.3.1"
},
"publishConfig": {
"access": "public"
}
}
41 changes: 41 additions & 0 deletions src/components/LabelWithHint.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import Button from '@codegouvfr/react-dsfr/Button'
import React, { useState } from 'react'
import { PropsWithChildren } from 'react'

type ElementWithHintProps = {
hintText: string | undefined
} & PropsWithChildren

export default function LabelWithHint({
hintText,
children,
}: ElementWithHintProps) {
const [showHint, setShowHint] = useState(false)

if (children == undefined) {
return null
}

return (
<div className="flex flex-col ">
<div className="flex" style={{ alignItems: 'center' }}>
{children}
{hintText ? (
<Button
iconId={
!showHint
? 'fr-icon-information-line'
: 'fr-icon-information-fill'
}
size="small"
priority="tertiary no outline"
onClick={() => setShowHint(!showHint)}
// NOTE: why the children prop is required for Button with icon?
children={null}
/>
) : null}
</div>
{showHint ? <p className="fr-hint-text">{hintText}</p> : null}
</div>
)
}
73 changes: 73 additions & 0 deletions src/components/TemplateArrayField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import Button from '@codegouvfr/react-dsfr/Button'
import Tabs from '@codegouvfr/react-dsfr/Tabs'
import { ArrayFieldTemplateProps } from '@rjsf/utils'
import React from 'react'

const defaultAddIcon = 'fr-icon-add-circle-line'
const defaultRemoveIcon = 'fr-icon-delete-line'

export default function ({
title,
uiSchema,
items,
canAdd,
onAddClick,
}: ArrayFieldTemplateProps & { removeIcon?: string; addIcon?: string }) {
const tabLabel = uiSchema !== undefined ? uiSchema['ui:tabLabel'] : 'Element'

return (
<div className="form-group field">
<div className="fr-input-group">
<label className="fr-label">{title}</label>
<div className="fr-input-wrap">
<Tabs
tabs={items
.map((element) => ({
label: `${tabLabel} ${element.index + 1}`,
content: (
<>
<Button
iconId={
uiSchema !== undefined
? uiSchema['ui:removeIcon']
: defaultRemoveIcon
}
onClick={element.onDropIndexClick(element.index)}
size="small"
priority="secondary"
>
Supprimer
</Button>
{element.children}
</>
),
}))
.concat([
{
label: `Ajouter`,
content: (
<>
{canAdd && (
<Button
iconId={
uiSchema !== undefined
? uiSchema['ui:addIcon']
: defaultAddIcon
}
onClick={onAddClick}
size="small"
priority="secondary"
>
Ajouter
</Button>
)}
</>
),
},
])}
/>
</div>
</div>
</div>
)
}
41 changes: 41 additions & 0 deletions src/components/TemplateBaseInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import Input from '@codegouvfr/react-dsfr/Input'
import { WidgetProps } from '@rjsf/utils'
import React from 'react'
import LabelWithHint from './LabelWithHint'

export default function ({
required,
placeholder,
type,
value,
onChange,
uiSchema,
label,
id,
...rest
}: WidgetProps) {
if (type === 'date') {
value =
uiSchema && uiSchema['ui:currentDate']
? value ?? new Date().toISOString().split('T')[0]
: value
onChange(value)
}

return (
<Input
nativeInputProps={{
type: type,
required: required,
placeholder: placeholder,
onChange: (e) => onChange(e.target.value),
min: rest.schema.minimum,
value: value,
}}
// NOTE: we don't want to display the label here because it is already
// displayed in the FieldTemplate (which manages the label and hint).
label={undefined}
{...rest}
/>
)
}
62 changes: 62 additions & 0 deletions src/components/TemplateField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { FieldTemplateProps } from '@rjsf/utils'
import React from 'react'
import LabelWithHint from './LabelWithHint'
import { Alert } from '@codegouvfr/react-dsfr/Alert'

// TODO: add to props
const schemaTypesToNotRenderTitles = ['boolean', 'array']

export default function ({
classNames,
style,
errors,
children,
id,
required,
label,
schema,
uiSchema,
}: FieldTemplateProps) {
let title: JSX.Element | null = null
let heading = uiSchema && uiSchema['ui:heading']

if (heading != undefined) {
switch (heading) {
case 'h3':
title = <h3 className="fr-h3">{label}</h3>
break
case 'h4':
title = <h4 className="fr-h4">{label}</h4>
break
case 'h5':
title = <h5 className="fr-h5">{label}</h5>
break
case 'h6':
title = <h6 className="fr-h6">{label}</h6>
break
default:
title = <h2 className="fr-h2">{label}</h2>
break
}
} else {
title = !schemaTypesToNotRenderTitles.includes(schema.type) ? (
!uiSchema || !uiSchema['ui:hideTitle'] ? (
<LabelWithHint hintText={uiSchema ? uiSchema['ui:help'] : undefined}>
<label htmlFor={id} className="fr-label">
{label + (required ? '*' : '')}
</label>
</LabelWithHint>
) : null
) : null
}

return (
<div className={classNames + ' fr-mt-1w'} style={style}>
{title}
{children}
{errors?.props?.errors != null && (
<Alert severity="error" description={errors} small />
)}
</div>
)
}
6 changes: 6 additions & 0 deletions src/components/TemplateTitleField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { TitleFieldProps } from '@rjsf/utils'

export default function (_: TitleFieldProps) {
// Legends and labels are managed directly by the widgets.
return null
}
32 changes: 32 additions & 0 deletions src/components/WidgetCheckBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react'
import { WidgetProps } from '@rjsf/utils'
import Checkbox from '@codegouvfr/react-dsfr/Checkbox'
import LabelWithHint from './LabelWithHint'

export default function (props: WidgetProps) {
return (
<div style={{ marginTop: '1rem', marginBottom: '-1rem' }}>
<Checkbox
options={[
{
label: (
<LabelWithHint
hintText={
props.uiSchema !== undefined
? props.uiSchema['ui:help']
: undefined
}
>
{props.schema.title + (props.required ? '*' : '')}
</LabelWithHint>
),
nativeInputProps: {
checked: props.value,
onChange: (e) => props.onChange(e.target.checked),
},
},
]}
/>
</div>
)
}
Loading

0 comments on commit 0107ebc

Please sign in to comment.