Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
900f7fe
feat: initial setup
g-saracca Sep 2, 2025
3115101
feat: add unit test for custom hook
g-saracca Sep 2, 2025
10b5d19
fix: remove unneeded loading check on button
g-saracca Sep 3, 2025
897ade1
feat(DesignSystem): support for value and label options
g-saracca Sep 3, 2025
20e1db4
feat: select template logic
g-saracca Sep 3, 2025
06dda56
feat: populating fields working
g-saracca Sep 4, 2025
d6d5dc7
feat: implement custom hook to prefill fields with user data in creat…
g-saracca Sep 4, 2025
3a3d564
refactor: avoid disabling button if user did not change the fields
g-saracca Sep 4, 2025
3938bc8
fix: avoid prefilling a subfield value with user data if siblings hav…
g-saracca Sep 4, 2025
8a57561
refactor: simplify defining metadata blocks info and default values i…
g-saracca Sep 4, 2025
d0b108b
fix: tweaks
g-saracca Sep 5, 2025
ca1f6eb
fix: replace dots in key names also and dont push the block if templa…
g-saracca Sep 5, 2025
6d294f7
fix: issue with facetable fields getting block info from normalized f…
g-saracca Sep 5, 2025
f3d528b
feat: add template field instructions
g-saracca Sep 5, 2025
ae7a915
chore: update keycloak dev-env with latest SPI and realm config
g-saracca Sep 8, 2025
2facdb7
test: add unit cases
g-saracca Sep 8, 2025
4725933
fix: update test-realm with curator user
g-saracca Sep 8, 2025
33a673e
test: improve coverage
g-saracca Sep 8, 2025
93ae527
refactor: update Dataset and License models for improved type usage
g-saracca Sep 10, 2025
e6eb006
chore: update to alpha version of js-dv
g-saracca Sep 10, 2025
b5f036e
change comment order
g-saracca Sep 12, 2025
337934b
test: add e2e test
g-saracca Sep 12, 2025
eb718ad
Merge branch 'develop' into feat/745-dataset-templates-and-create-dat…
g-saracca Sep 29, 2025
d2699d0
docs: added changelog
g-saracca Sep 29, 2025
ed61156
docs: add to changelog
g-saracca Sep 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ This changelog follows the principles of [Keep a Changelog](https://keepachangel

### Added

- Dataset Templates Selector in the Create Dataset page.

### Changed

### Fixed
Expand Down
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"@dnd-kit/sortable": "8.0.0",
"@dnd-kit/utilities": "3.2.2",
"@faker-js/faker": "7.6.0",
"@iqss/dataverse-client-javascript": "2.0.0-alpha.62",
"@iqss/dataverse-client-javascript": "2.0.0-alpha.66",
"@iqss/dataverse-design-system": "*",
"@istanbuljs/nyc-config-typescript": "1.0.2",
"@tanstack/react-table": "8.9.2",
Expand Down
4 changes: 3 additions & 1 deletion packages/design-system/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
- Add `align` prop to control the alignment of the dropdown menu.
- **DropdownButtonItem:**
- Add `type` prop to allow specifying the type of the element.
- **SelectAdvanced:** Fix word wrapping in options list to prevent overflow and ensure long text is displayed correctly.
- **SelectAdvanced:**
- Fix word wrapping in options list to prevent overflow and ensure long text is displayed correctly.
- Support for options with a shape of `{ label: string; value: string; }[]` instead of just `string[]`.

# [2.0.2](https://github.com/IQSS/dataverse-frontend/compare/@iqss/dataverse-design-system@2.0.1...@iqss/dataverse-design-system@2.0.2) (2024-06-23)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,47 +12,43 @@ import {
} from './selectAdvancedReducer'
import { SelectAdvancedToggle } from './SelectAdvancedToggle'
import { SelectAdvancedMenu } from './SelectAdvancedMenu'
import { areArraysEqual, debounce } from './utils'
import { areOptionArraysEqual, debounce, normalizeOptions } from './utils'
import { useIsFirstRender } from './useIsFirstRender'

export const DEFAULT_LOCALES = {
select: 'Select...'
export const DEFAULT_LOCALES = { select: 'Select...' }
export const SELECT_MENU_SEARCH_DEBOUNCE_TIME = 400

export type Option = { value: string; label: string }

export type InputOptions = string[] | Option[]

type BaseProps = {
options: InputOptions
isSearchable?: boolean
isDisabled?: boolean
isInvalid?: boolean
inputButtonId?: string
locales?: { select?: string }
}

export const SELECT_MENU_SEARCH_DEBOUNCE_TIME = 400
type SingleProps = BaseProps & {
isMultiple?: false
onChange?: (selected: string) => void
defaultValue?: string
}

export type SelectAdvancedProps =
| {
isMultiple?: false
options: string[]
onChange?: (selected: string) => void
defaultValue?: string
isSearchable?: boolean
isDisabled?: boolean
isInvalid?: boolean
inputButtonId?: string
locales?: {
select?: string
}
}
| {
isMultiple: true
options: string[]
onChange?: (selected: string[]) => void
defaultValue?: string[]
isSearchable?: boolean
isDisabled?: boolean
isInvalid?: boolean
inputButtonId?: string
locales?: {
select?: string
}
}
type MultipleProps = BaseProps & {
isMultiple: true
onChange?: (selected: string[]) => void
defaultValue?: string[]
}

export type SelectAdvancedProps = SingleProps | MultipleProps

export const SelectAdvanced = forwardRef(
(
{
options: propsOption,
options: propsOptions,
onChange,
defaultValue,
isMultiple,
Expand All @@ -64,15 +60,16 @@ export const SelectAdvanced = forwardRef(
}: SelectAdvancedProps,
ref: ForwardedRef<HTMLInputElement | null>
) => {
const dynamicInitialOptions = useMemo(() => {
return isMultiple ? propsOption : [locales?.select ?? DEFAULT_LOCALES.select, ...propsOption]
}, [isMultiple, propsOption, locales])
const normalizedOptions: Option[] = useMemo(
() => normalizeOptions(propsOptions),
[propsOptions]
)

const [{ selected, filteredOptions, searchValue, options }, dispatch] = useReducer(
selectAdvancedReducer,
getSelectAdvancedInitialState(
Boolean(isMultiple),
dynamicInitialOptions,
normalizedOptions,
locales?.select ?? DEFAULT_LOCALES.select,
defaultValue
)
Expand All @@ -81,107 +78,89 @@ export const SelectAdvanced = forwardRef(
const isFirstRender = useIsFirstRender()
const menuId = useId()

const callOnChage = useCallback(
const callOnChange = useCallback(
(newSelected: string | string[]): void => {
if (!onChange) return
//@ts-expect-error - types differs
// @ts-expect-error - union narrowing en runtime
onChange(newSelected)
},
[onChange]
)

useEffect(() => {
const optionsRemainTheSame = areArraysEqual(dynamicInitialOptions, options)

// If the options remain the same, do nothing
const optionsRemainTheSame = areOptionArraysEqual(normalizedOptions, options)
if (optionsRemainTheSame) return

const selectedOptionsThatAreNotInNewOptions = isMultiple
? (selected as string[]).filter((option) => !dynamicInitialOptions.includes(option))
: []

// If there are selected options that are not in the new options, remove them
if (isMultiple && selectedOptionsThatAreNotInNewOptions.length > 0) {
selectedOptionsThatAreNotInNewOptions.forEach((option) => dispatch(removeOption(option)))

const newSelected = (selected as string[]).filter((option) =>
dynamicInitialOptions.includes(option)
)
const optionValues = new Set(normalizedOptions.map((o) => o.value))

callOnChage(newSelected)
if (isMultiple) {
const selectedValues = selected as string[]
const outOfNewOptions = selectedValues.filter((v) => !optionValues.has(v))
if (outOfNewOptions.length > 0) {
const newSelected = selectedValues.filter((v) => optionValues.has(v))
callOnChange(newSelected)
outOfNewOptions.forEach((v) => dispatch(removeOption(v)))
}
} else {
const current = selected as string
if (current !== '' && !optionValues.has(current)) {
dispatch(selectOption(''))
callOnChange('')
}
}

// If the selected option is not in the new options replace it with the default empty value
if (
!isMultiple &&
selected !== '' &&
!dynamicInitialOptions.some((option) => option === (selected as string))
) {
dispatch(selectOption(''))
callOnChage('')
}
dispatch(updateOptions(dynamicInitialOptions))
}, [dynamicInitialOptions, options, selected, isFirstRender, dispatch, callOnChage, isMultiple])
dispatch(updateOptions(normalizedOptions))
}, [normalizedOptions, options, selected, isFirstRender, callOnChange, isMultiple])

const handleSearch = debounce((e: React.ChangeEvent<HTMLInputElement>): void => {
const { value } = e.target
dispatch(searchOptions(value))
}, SELECT_MENU_SEARCH_DEBOUNCE_TIME)

// ONLY FOR MULTIPLE SELECT 👇
// MULTIPLE
const handleCheck = (e: React.ChangeEvent<HTMLInputElement>): void => {
const { value, checked } = e.target

if (checked) {
const newSelected = [...(selected as string[]), value]
callOnChage(newSelected)

callOnChange(newSelected)
dispatch(selectOption(value))
} else {
const newSelected = (selected as string[]).filter((option) => option !== value)
callOnChage(newSelected)

const newSelected = (selected as string[]).filter((v) => v !== value)
callOnChange(newSelected)
dispatch(removeOption(value))
}
}

// ONLY FOR SINGLE SELECT 👇
const handleClickOption = (option: string): void => {
if ((selected as string) === option) {
return
}
callOnChage(option)

dispatch(selectOption(option))
// SINGLE
const handleClickOption = (value: string): void => {
if ((selected as string) === value) return
callOnChange(value)
dispatch(selectOption(value))
}

// ONLY FOR MULTIPLE SELECT 👇
const handleRemoveSelectedOption = (option: string): void => {
const newSelected = (selected as string[]).filter((selected) => selected !== option)
callOnChage(newSelected)

dispatch(removeOption(option))
// MULTIPLE
const handleRemoveSelectedOption = (value: string): void => {
const newSelected = (selected as string[]).filter((v) => v !== value)
callOnChange(newSelected)
dispatch(removeOption(value))
}

// ONLY FOR MULTIPLE SELECT 👇
// MULTIPLE
const handleToggleAllOptions = (e: React.ChangeEvent<HTMLInputElement>): void => {
if (e.target.checked) {
const newSelected =
filteredOptions.length > 0
? Array.from(new Set([...(selected as string[]), ...filteredOptions]))
: options

callOnChage(newSelected)

const source = filteredOptions.length > 0 ? filteredOptions : options
const newSelected = Array.from(
new Set([...(selected as string[]), ...source.map((o) => o.value)])
)
callOnChange(newSelected)
dispatch(selectAllOptions())
} else {
const toRemove = new Set(
(filteredOptions.length > 0 ? filteredOptions : options).map((o) => o.value)
)
const newSelected =
filteredOptions.length > 0
? (selected as string[]).filter((option) => !filteredOptions.includes(option))
: []

callOnChage(newSelected)

filteredOptions.length > 0 ? (selected as string[]).filter((v) => !toRemove.has(v)) : []
callOnChange(newSelected)
dispatch(deselectAllOptions())
}
}
Expand All @@ -193,6 +172,7 @@ export const SelectAdvanced = forwardRef(
<SelectAdvancedToggle
isMultiple={Boolean(isMultiple)}
selected={selected}
options={options}
handleRemoveSelectedOption={handleRemoveSelectedOption}
isInvalid={isInvalid}
isDisabled={isDisabled}
Expand Down
Loading
Loading