Skip to content
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

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
7 changes: 7 additions & 0 deletions elements/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@
"./pf-clipboard-copy/pf-clipboard-copy.js": "./pf-clipboard-copy/pf-clipboard-copy.js",
"./pf-code-block/BaseCodeBlock.js": "./pf-code-block/BaseCodeBlock.js",
"./pf-code-block/pf-code-block.js": "./pf-code-block/pf-code-block.js",
"./pf-date-picker/date-picker-helper.js": "./pf-date-picker/date-picker-helper.js",
"./pf-date-picker/pf-calendar.js": "./pf-date-picker/pf-calendar.js",
"./pf-date-picker/pf-date-picker.js": "./pf-date-picker/pf-date-picker.js",
"./pf-date-picker/pf-month-select.js": "./pf-date-picker/pf-month-select.js",
"./pf-date-picker/pf-next-button.js": "./pf-date-picker/pf-next-button.js",
"./pf-date-picker/pf-previous-button.js": "./pf-date-picker/pf-previous-button.js",
"./pf-date-picker/pf-year-input.js": "./pf-date-picker/pf-year-input.js",
Copy link
Member

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 carefully

Copy link
Collaborator Author

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.

"./pf-dropdown/pf-dropdown.js": "./pf-dropdown/pf-dropdown.js",
"./pf-dropdown/pf-dropdown-group.js": "./pf-dropdown/pf-dropdown-group.ts",
"./pf-dropdown/pf-dropdown-menu.js": "./pf-dropdown/pf-dropdown-menu.ts",
Expand Down
11 changes: 11 additions & 0 deletions elements/pf-date-picker/README.md
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be updated with prose from v4-archive.patternfly.org

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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>
```
342 changes: 342 additions & 0 deletions elements/pf-date-picker/date-picker-helper.ts
Copy link
Member

Choose a reason for hiding this comment

The 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;
};
23 changes: 23 additions & 0 deletions elements/pf-date-picker/demo/demo.css
Copy link
Member

Choose a reason for hiding this comment

The 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

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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)
}
Loading
Loading