-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add ember power calendar wip wip arrows next to month info Have 2 diff centers clean up css pass attributes fix start and end date logic add fixed width remove @center arg * add icons * add type * fix lint * add aria-label for accesibility * add aria label for nav tag * typo * aria attributes fundamentally not working with ember power calendar. So ignore * ignore test not lint * ignore in a11y testing
- Loading branch information
1 parent
269fb65
commit c639284
Showing
14 changed files
with
478 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
179 changes: 179 additions & 0 deletions
179
packages/boxel-ui/addon/src/components/date-range-picker/index.gts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
import { fn } from '@ember/helper'; | ||
import { on } from '@ember/modifier'; | ||
import { action } from '@ember/object'; | ||
import Component from '@glimmer/component'; | ||
import { tracked } from '@glimmer/tracking'; | ||
import PowerCalendarRange from 'ember-power-calendar/components/power-calendar-range'; | ||
import { type TPowerCalendarRangeOnSelect } from 'ember-power-calendar/components/power-calendar-range'; | ||
import powerCalendarFormatDate from 'ember-power-calendar/helpers/power-calendar-format-date'; | ||
import { | ||
type SelectedPowerCalendarRange, | ||
add, | ||
} from 'ember-power-calendar/utils'; | ||
|
||
import TriangleLeftIcon from '../../icons/triangle-left.gts'; | ||
import TriangleRightIcon from '../../icons/triangle-right.gts'; | ||
import IconButton from '../icon-button/index.gts'; | ||
|
||
interface Signature { | ||
Args: { | ||
end?: Date | null; | ||
onSelect: TPowerCalendarRangeOnSelect; | ||
selected?: SelectedPowerCalendarRange; | ||
start?: Date | null; | ||
}; | ||
Element: HTMLElement; | ||
} | ||
|
||
export default class DateRangePicker extends Component<Signature> { | ||
@tracked leftCenter: Date; | ||
@tracked rightCenter: Date; | ||
|
||
constructor(owner: any, args: any) { | ||
super(owner, args); | ||
// If both start and end are provided, use them | ||
if (this.args.start && this.args.end) { | ||
this.leftCenter = this.args.start; | ||
this.rightCenter = this.args.end; | ||
} | ||
// If only start is provided, set right center to next month | ||
else if (this.args.start) { | ||
this.leftCenter = this.args.start; | ||
this.rightCenter = add(this.args.start, 1, 'month'); | ||
} | ||
// If only end is provided, set left center to previous month | ||
else if (this.args.end) { | ||
this.rightCenter = this.args.end; | ||
this.leftCenter = add(this.args.end, -1, 'month'); | ||
} | ||
// If neither is provided, use current date and next month | ||
else { | ||
const today = new Date(); | ||
this.leftCenter = today; | ||
this.rightCenter = add(today, 1, 'month'); | ||
} | ||
} | ||
|
||
@action | ||
onNavigate(side: 'left' | 'right', direction: 'previous' | 'next') { | ||
const months = direction === 'next' ? 1 : -1; | ||
|
||
if (side === 'left') { | ||
const newLeftCenter = add(this.leftCenter, months, 'month'); | ||
this.leftCenter = newLeftCenter; | ||
|
||
// If left month would overlap with right month, push right month forward | ||
if (newLeftCenter >= this.rightCenter) { | ||
this.rightCenter = add(newLeftCenter, 1, 'month'); | ||
} | ||
} else { | ||
const newRightCenter = add(this.rightCenter, months, 'month'); | ||
this.rightCenter = newRightCenter; | ||
|
||
// If right month would overlap with left month, push left month backward | ||
if (newRightCenter <= this.leftCenter) { | ||
this.leftCenter = add(newRightCenter, -1, 'month'); | ||
} | ||
} | ||
} | ||
|
||
<template> | ||
<div class='date-range-picker'> | ||
<PowerCalendarRange | ||
@selected={{@selected}} | ||
@onSelect={{@onSelect}} | ||
@locale='en-US' | ||
...attributes | ||
as |calendar| | ||
> | ||
<div class='months-container'> | ||
<div> | ||
<calendar.Nav> | ||
<div class='nav-container'> | ||
<IconButton | ||
@icon={{TriangleLeftIcon}} | ||
aria-label='Previous month' | ||
{{on 'click' (fn this.onNavigate 'left' 'previous')}} | ||
/> | ||
<div class='month-name'> | ||
{{powerCalendarFormatDate | ||
this.leftCenter | ||
'MMMM yyyy' | ||
locale=calendar.locale | ||
}} | ||
</div> | ||
<IconButton | ||
@icon={{TriangleRightIcon}} | ||
aria-label='Next month' | ||
{{on 'click' (fn this.onNavigate 'left' 'next')}} | ||
/> | ||
</div> | ||
</calendar.Nav> | ||
<calendar.Days @center={{this.leftCenter}} /> | ||
</div> | ||
|
||
<div> | ||
<calendar.Nav> | ||
<div class='nav-container'> | ||
<IconButton | ||
@icon={{TriangleLeftIcon}} | ||
aria-label='Previous month' | ||
{{on 'click' (fn this.onNavigate 'right' 'previous')}} | ||
/> | ||
<div class='month-name'> | ||
{{powerCalendarFormatDate | ||
this.rightCenter | ||
'MMMM yyyy' | ||
locale=calendar.locale | ||
}} | ||
</div> | ||
<IconButton | ||
@icon={{TriangleRightIcon}} | ||
aria-label='Next month' | ||
{{on 'click' (fn this.onNavigate 'right' 'next')}} | ||
/> | ||
</div> | ||
</calendar.Nav> | ||
<calendar.Days @center={{this.rightCenter}} /> | ||
</div> | ||
</div> | ||
</PowerCalendarRange> | ||
</div> | ||
<style scoped> | ||
.date-range-picker { | ||
width: 100%; | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
} | ||
.month-name { | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
} | ||
.months-container { | ||
display: flex; | ||
flex-direction: row; | ||
align-items: flex-start; | ||
gap: var(--boxel-sp-lg); | ||
} | ||
.nav-container { | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
} | ||
</style> | ||
{{! template-lint-disable require-scoped-style }} | ||
<style> | ||
.ember-power-calendar-day { | ||
width: 2.5em; /*add fixed width to ensure cols of numbers align*/ | ||
padding: var(--boxel-sp-xxs); | ||
} | ||
.ember-power-calendar-week { | ||
gap: var(--boxel-sp-xxs); | ||
} | ||
</style> | ||
</template> | ||
} |
102 changes: 102 additions & 0 deletions
102
packages/boxel-ui/addon/src/components/date-range-picker/usage.gts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import { action } from '@ember/object'; | ||
import Component from '@glimmer/component'; | ||
import { tracked } from '@glimmer/tracking'; | ||
import FreestyleUsage from 'ember-freestyle/components/freestyle/usage'; | ||
import type { | ||
NormalizeRangeActionValue, | ||
SelectedPowerCalendarRange, | ||
} from 'ember-power-calendar/utils'; | ||
|
||
import DateRangePicker from './index.gts'; | ||
|
||
export default class DateRangePickerUsage extends Component { | ||
@tracked range1: SelectedPowerCalendarRange = { | ||
start: new Date(2024, 10, 1), | ||
end: new Date(2024, 10, 15), | ||
}; | ||
@tracked range2: SelectedPowerCalendarRange = { | ||
start: new Date(2024, 9, 15), | ||
end: new Date(2024, 12, 15), | ||
}; | ||
@tracked range3: SelectedPowerCalendarRange | undefined; | ||
|
||
@action | ||
onSelect1(selected: NormalizeRangeActionValue) { | ||
this.range1 = selected.date; | ||
} | ||
|
||
@action | ||
onSelect2(selected: NormalizeRangeActionValue) { | ||
this.range2 = selected.date; | ||
} | ||
|
||
@action | ||
onSelect3(selected: NormalizeRangeActionValue) { | ||
this.range3 = selected.date; | ||
} | ||
|
||
parseDate(date: Date | null | undefined) { | ||
if (!date) { | ||
return ''; | ||
} | ||
const formatter = new Intl.DateTimeFormat('en-US', { | ||
year: 'numeric', | ||
month: 'short', | ||
day: 'numeric', | ||
}); | ||
return formatter.format(date); | ||
} | ||
|
||
get range1String() { | ||
return `${this.parseDate(this.range1.start)} - ${this.parseDate( | ||
this.range1.end, | ||
)}`; | ||
} | ||
|
||
get range2String() { | ||
return `${this.parseDate(this.range2.start)} - ${this.parseDate( | ||
this.range2.end, | ||
)}`; | ||
} | ||
|
||
get range3String() { | ||
if (!this.range3) { | ||
return `No date selected`; | ||
} | ||
return `${this.parseDate(this.range3.start)} - ${this.parseDate( | ||
this.range3.end, | ||
)}`; | ||
} | ||
|
||
<template> | ||
<FreestyleUsage @name='Date Range Picker (within month)'> | ||
<:example> | ||
{{this.range1String}} | ||
<DateRangePicker | ||
@selected={{this.range1}} | ||
@onSelect={{this.onSelect1}} | ||
/> | ||
</:example> | ||
</FreestyleUsage> | ||
<FreestyleUsage @name='Date Range Picker (across months)'> | ||
<:example> | ||
{{this.range2String}} | ||
<DateRangePicker | ||
@selected={{this.range2}} | ||
@onSelect={{this.onSelect2}} | ||
@start={{this.range2.start}} | ||
@end={{this.range2.end}} | ||
/> | ||
</:example> | ||
</FreestyleUsage> | ||
<FreestyleUsage @name='Date Range Picker (no date specified)'> | ||
<:example> | ||
{{this.range3String}} | ||
<DateRangePicker | ||
@selected={{this.range3}} | ||
@onSelect={{this.onSelect3}} | ||
/> | ||
</:example> | ||
</FreestyleUsage> | ||
</template> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// This file is auto-generated by 'pnpm rebuild:icons' | ||
import type { TemplateOnlyComponent } from '@ember/component/template-only'; | ||
|
||
import type { Signature } from './types.ts'; | ||
|
||
const IconComponent: TemplateOnlyComponent<Signature> = <template> | ||
<svg | ||
xmlns='http://www.w3.org/2000/svg' | ||
viewBox='-3 -3 16 12' | ||
...attributes | ||
><path | ||
fill='var(--icon-fill, #000)' | ||
stroke='var(--icon-color,#000)' | ||
stroke-linecap='round' | ||
stroke-linejoin='round' | ||
stroke-width='2' | ||
d='m5.414 6.414-4-4 4-4v8Z' | ||
/></svg> | ||
</template>; | ||
|
||
// @ts-expect-error this is the only way to set a name on a Template Only Component currently | ||
IconComponent.name = 'TriangleLeft'; | ||
export default IconComponent; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// This file is auto-generated by 'pnpm rebuild:icons' | ||
import type { TemplateOnlyComponent } from '@ember/component/template-only'; | ||
|
||
import type { Signature } from './types.ts'; | ||
|
||
const IconComponent: TemplateOnlyComponent<Signature> = <template> | ||
<svg | ||
xmlns='http://www.w3.org/2000/svg' | ||
viewBox='-3 -3 16 12' | ||
...attributes | ||
><path | ||
fill='var(--icon-fill, #000)' | ||
stroke='var(--icon-color,#000)' | ||
stroke-linecap='round' | ||
stroke-linejoin='round' | ||
stroke-width='2' | ||
d='m1.414 6.414 4-4-4-4v8Z' | ||
/></svg> | ||
</template>; | ||
|
||
// @ts-expect-error this is the only way to set a name on a Template Only Component currently | ||
IconComponent.name = 'TriangleRight'; | ||
export default IconComponent; |
Oops, something went wrong.