-
Notifications
You must be signed in to change notification settings - Fork 96
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(date-picker): add pf-date-picker #2599
base: main
Are you sure you want to change the base?
Changes from 4 commits
ad5106e
2b0ff7d
690a889
832d1f6
549822b
36f9aca
08820f3
19e0e11
1ae65c5
52456d7
0c94f75
dde0275
988447d
412b41d
543394f
80615e4
c04282e
9580ff1
e9e9833
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should be updated with prose from v4-archive.patternfly.org There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated README file |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# Date Picker | ||
Add a description of the component here. | ||
|
||
## Usage | ||
Describe how best to use this web component along with best practices. | ||
|
||
```html | ||
<pf-date-picker> | ||
|
||
</pf-date-picker> | ||
``` |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note to future self and other reviewers: see if / how many of these functions can be replaced with Intl built-ins. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,342 @@ | ||
export interface DateFormatDetails { | ||
dateParts: string[]; | ||
literal: string | undefined; | ||
} | ||
|
||
export interface InputDate { | ||
day: number; | ||
month: number; | ||
year: number; | ||
literal: string; | ||
} | ||
|
||
export const days: string[] = ['S', 'M', 'T', 'W', 'T', 'F', 'S']; | ||
|
||
// Function to return date object | ||
export const getFormattedDate = (date: Date) => { | ||
const focusDate = { | ||
day: date.getDate(), | ||
month: date.getMonth(), | ||
year: date.getFullYear() | ||
}; | ||
|
||
return focusDate; | ||
}; | ||
|
||
|
||
// Function to get the date format locale parts | ||
export const getLocaleParts = (language?: string) => { | ||
// Get the browser user locale - Commented for reference | ||
// const userLocale: string = navigator.languages && navigator.languages.length ? | ||
// navigator.languages[0] : navigator.language; | ||
|
||
const { timeZone } = Intl.DateTimeFormat().resolvedOptions(); | ||
let formatter: Intl.DateTimeFormatPart[] = []; | ||
|
||
// Set date format options | ||
const options: Intl.DateTimeFormatOptions = { | ||
timeZone: timeZone, | ||
dateStyle: 'short', | ||
localeMatcher: 'lookup' | ||
}; | ||
|
||
// If there is locale passed from parent, pass locale else use "default" | ||
const locale: string = language ? language : 'default'; | ||
|
||
// Try - Catch block is used to catch error and format date using default locale if invalid locale is passed from parent | ||
try { | ||
// Get the date components breakdown array | ||
formatter = new Intl.DateTimeFormat(locale, options).formatToParts(new Date()); | ||
} catch (error) { | ||
if (error) { // Get the date components breakdown array with default locale | ||
formatter = new Intl.DateTimeFormat('default', options).formatToParts(new Date()); | ||
} | ||
} | ||
|
||
return formatter; | ||
}; | ||
|
||
|
||
// Function to return the date values from user input | ||
export const getDateValues = (dateString: string, languageCode?: string, dateFormatInput?: string) => { | ||
let parseDay!: number; | ||
let parseMonth!: number; | ||
let parseYear!: number; | ||
let splitWith!: string; | ||
|
||
// If there is a dateFormat input from parent, the input format will be applied | ||
// else date format will be generated and applied from languageCode input or default locale | ||
if (dateFormatInput && isInputDateFormatValid(dateFormatInput)) { | ||
const dateFormatDetails: DateFormatDetails = parseDateFormat(dateFormatInput); | ||
let index = 0; | ||
|
||
dateFormatDetails.dateParts.map((part: string) => { // Generate the date format | ||
switch (part) { | ||
case 'MM': | ||
parseMonth = index; | ||
index++; | ||
break; | ||
case 'DD': | ||
parseDay = index; | ||
index++; | ||
break; | ||
case 'YYYY': | ||
parseYear = index; | ||
index++; | ||
break; | ||
default: | ||
break; | ||
} | ||
}); | ||
splitWith = dateFormatDetails.literal ? dateFormatDetails.literal : '/'; | ||
} else { | ||
const formatter: Intl.DateTimeFormatPart[] = getLocaleParts(languageCode); | ||
let index = 0; | ||
|
||
formatter.map((part: Intl.DateTimeFormatPart) => { // Generate the date format | ||
switch (part.type) { | ||
case 'month': | ||
parseMonth = index; | ||
index++; | ||
break; | ||
case 'day': | ||
parseDay = index; | ||
index++; | ||
break; | ||
case 'year': | ||
parseYear = index; | ||
index++; | ||
break; | ||
default: | ||
splitWith = part.value; | ||
} | ||
}); | ||
} | ||
|
||
const dateStringArray: string[] = dateString.split(splitWith); | ||
const selectedDayInput: number = parseInt(dateStringArray[parseDay], 10); | ||
const selectedMonthInput: number = parseInt(dateStringArray[parseMonth], 10); | ||
const selectedYearInput: number = parseInt(dateStringArray[parseYear], 10); | ||
const inputDate: InputDate = { | ||
day: selectedDayInput, | ||
month: selectedMonthInput, | ||
year: selectedYearInput, | ||
literal: splitWith | ||
}; | ||
|
||
return inputDate; | ||
}; | ||
|
||
// Function to return the fomatted date string | ||
export const getDateFormat = (day: string, month: string, year: string, languageCode?: string, dateFormatInput?: string) => { | ||
let formattedDate = ``; | ||
const dd: string = day; | ||
const mm: string = month; | ||
const yyyy: string = year; | ||
|
||
// If there is a dateFormat input from parent, the input format will be applied | ||
// else date format will be generated and applied from languageCode input or default locale | ||
if (dateFormatInput && isInputDateFormatValid(dateFormatInput)) { | ||
const dateFormatDetails: DateFormatDetails = parseDateFormat(dateFormatInput); | ||
|
||
dateFormatDetails.dateParts.map((part: string, index: number) => { // Generate the date format | ||
switch (part) { | ||
case 'MM': | ||
formattedDate += `${mm}`; | ||
break; | ||
case 'DD': | ||
formattedDate += `${dd}`; | ||
break; | ||
case 'YYYY': | ||
formattedDate += `${yyyy}`; | ||
break; | ||
default: | ||
break; | ||
} | ||
if (index < 2) { | ||
formattedDate += dateFormatDetails.literal ? dateFormatDetails.literal : '/'; | ||
} | ||
}); | ||
} else { | ||
const formatter: Intl.DateTimeFormatPart[] = getLocaleParts(languageCode); | ||
|
||
formatter.map((part: Intl.DateTimeFormatPart) => { // Generate the date format | ||
switch (part.type) { | ||
case 'month': | ||
formattedDate += `${mm}`; | ||
break; | ||
case 'day': | ||
formattedDate += `${dd}`; | ||
break; | ||
case 'year': | ||
formattedDate += `${yyyy}`; | ||
break; | ||
default: | ||
formattedDate += part.value; | ||
} | ||
}); | ||
} | ||
|
||
return formattedDate; | ||
}; | ||
|
||
// Function to return date format based on date format input or locale | ||
export const getDatePatternFromLocale = (languageCode?: string, dateFormatInput?: string) => { | ||
let localeDateFormat = ''; | ||
|
||
// If there is a dateFormat input from parent, the input format will be applied | ||
// else date format will be generated and applied from languageCode input or default locale | ||
if (dateFormatInput && isInputDateFormatValid(dateFormatInput)) { | ||
localeDateFormat = dateFormatInput; | ||
} else { | ||
const formatter: Intl.DateTimeFormatPart[] = getLocaleParts(languageCode); | ||
|
||
formatter.map((part: Intl.DateTimeFormatPart) => { // Generate the date format | ||
switch (part.type) { | ||
case 'month': | ||
localeDateFormat += 'MM'; | ||
break; | ||
case 'day': | ||
localeDateFormat += 'DD'; | ||
break; | ||
case 'year': | ||
localeDateFormat += 'YYYY'; | ||
break; | ||
default: | ||
localeDateFormat += part.value; | ||
} | ||
}); | ||
} | ||
|
||
return localeDateFormat; | ||
}; | ||
|
||
// Function to generate regex pattern based on date format input or locale | ||
export const getRegexPattern = (languageCode?: string, dateFormatInput?: string) => { | ||
const regDay = '[0-9]{1,2}'; | ||
const regMonth = '[0-9]{1,2}'; | ||
const regYear = '[0-9]{2,4}'; | ||
let regex = '^'; | ||
|
||
// If there is a dateFormat input from parent, the input format will be applied | ||
// else date format will be generated and applied from languageCode input or default locale | ||
if (dateFormatInput && isInputDateFormatValid(dateFormatInput)) { | ||
const dateFormatDetails: DateFormatDetails = parseDateFormat(dateFormatInput); | ||
|
||
dateFormatDetails.dateParts.map((part: string, index: number) => { // Generate the date format | ||
switch (part) { | ||
case 'MM': | ||
regex += regMonth; | ||
break; | ||
case 'DD': | ||
regex += regDay; | ||
break; | ||
case 'YYYY': | ||
regex += regYear; | ||
break; | ||
default: | ||
break; | ||
} | ||
if (index < 2) { | ||
regex += dateFormatDetails.literal ? dateFormatDetails.literal : '/'; | ||
} | ||
}); | ||
} else { | ||
const formatter: Intl.DateTimeFormatPart[] = getLocaleParts(languageCode); | ||
|
||
formatter.map((part: Intl.DateTimeFormatPart) => { // Generate the regex for the date format according to locale | ||
switch (part.type) { | ||
case 'month': | ||
regex += regMonth; | ||
break; | ||
case 'day': | ||
regex += regDay; | ||
break; | ||
case 'year': | ||
regex += regYear; | ||
break; | ||
default: | ||
regex += part.value; // Will append the part literal '/' or '.' or '-' to the regex | ||
break; | ||
} | ||
}); | ||
} | ||
regex += '$'; | ||
|
||
return regex; | ||
}; | ||
|
||
// Function to generate date parts array from date format input | ||
export const parseDateFormat = (dateFormatInput: string) => { | ||
const literals: string[] = ['/', '-', '.']; // Supported date format literals | ||
let datePartsArray: string[] = []; | ||
|
||
const literal = literals.find((literal: string) => { // Find the literal in the format | ||
if (dateFormatInput.includes(literal)) { | ||
return literal; | ||
} | ||
}); | ||
|
||
if (literal) { | ||
datePartsArray = dateFormatInput.split(literal); // Split the format to date parts | ||
} | ||
|
||
const dateFormatParts = { | ||
dateParts: datePartsArray, | ||
literal: literal | ||
}; | ||
|
||
return dateFormatParts; | ||
}; | ||
|
||
// Function to check validity of the date format input | ||
export const isInputDateFormatValid = (dateFormatInput: string) => { | ||
let isDateFormatValid = false; | ||
const supportedDateFormats: string[] = [ // Supported date formats from parent | ||
'DD/MM/YYYY', 'MM/DD/YYYY', 'YYYY/MM/DD', 'YYYY/DD/MM', 'DD-MM-YYYY', 'MM-DD-YYYY', | ||
'YYYY-MM-DD', 'YYYY-DD-MM', 'DD.MM.YYYY', 'MM.DD.YYYY', 'YYYY.MM.DD', 'YYYY.DD.MM' | ||
]; | ||
|
||
if (supportedDateFormats.includes(dateFormatInput)) { | ||
isDateFormatValid = true; | ||
} else { | ||
isDateFormatValid = false; | ||
} | ||
|
||
return isDateFormatValid; | ||
}; | ||
|
||
export const getMonthNamesFromLocale = (languageCode?: string) => { | ||
const monthNames: string[] = [ | ||
'January', | ||
'February', | ||
'March', | ||
'April', | ||
'May', | ||
'June', | ||
'July', | ||
'August', | ||
'September', | ||
'October', | ||
'November', | ||
'December' | ||
]; | ||
|
||
if (languageCode) { | ||
const date: Date = new Date(); | ||
try { | ||
const translatedMonthNames: string[] = []; | ||
for (let i = 0; i < 12; i++) { | ||
date.setMonth(i); | ||
const monthName: string = new Intl.DateTimeFormat(languageCode, { month: 'long' }).format(new Date(date.getFullYear(), i, date.getDate())); | ||
translatedMonthNames.push(monthName); | ||
} | ||
return translatedMonthNames; | ||
} catch (error) { | ||
if (error) { | ||
return monthNames; | ||
} | ||
} | ||
} | ||
return monthNames; | ||
}; |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should prefer to inline all the css and js for the demos directly into the html files There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added inline css and js for the html demo files. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
.container { | ||
min-height: 25vh; | ||
height: auto; | ||
width: 100%; | ||
display: flex; | ||
align-items: flex-start; | ||
justify-content: flex-start; | ||
padding: 40px; | ||
flex-direction: column; | ||
} | ||
|
||
.container section { | ||
margin-bottom: var(--rh-space-lg, 16px) | ||
} | ||
|
||
.container section p span { | ||
color: var(--rh-color-text-secondary-on-light, #4d4d4d); | ||
font-family: var(--rh-font-family-code, RedHatMono, "Red Hat Mono", "Courier New", Courier, monospace); | ||
} | ||
|
||
.container section pre { | ||
padding-bottom: var(--rh-space-md, 8px) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since upstream only provides the
DatePicker
react component, we should also try to limit the number of public components we ship here. We may make an exception if, for accessibility reasons, we need to put these elements in the same root. However, in the case of date picker, We can probably allow ourselves the luxury of doing everything in the same shadow root. We'll need to investigate that carefullyThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Removed the child components exports list.