Skip to content

Commit

Permalink
feat: Rating improvements
Browse files Browse the repository at this point in the history
* Selecting the same value will reset the rating
* Rated/unrated symbols accept a callback function that can
resolve the symbol for the appropriate position
* Added event on hover which returns the current "active" index
  • Loading branch information
rkaraivanov committed Nov 18, 2021
1 parent 5d9890c commit 8951170
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 21 deletions.
12 changes: 12 additions & 0 deletions scripts/build-stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,18 @@ function extractTags(meta) {
return {
component: meta.name,
args: Array.from(meta.properties || [])
// Strip function types from the storybook generation
// TODO: Revise this whole pipeline and refactor it as it becomes unwieldy
.map((prop) => {
if (prop.type.includes('(')) {
prop.type = prop.type.split('|')
.map(part => part.trim())
.filter(part => !part.startsWith('('))
.join(' | ');
return prop;
}
return prop;
})
.filter(
(prop) =>
SUPPORTED_TYPES.includes(prop.type) ||
Expand Down
34 changes: 28 additions & 6 deletions src/components/rating/rating.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import IgcIconComponent from '../icon/icon';

export interface IgcRatingEventMap {
igcChange: CustomEvent<number>;
igcHover: CustomEvent<number>;
}

/**
Expand Down Expand Up @@ -46,13 +47,21 @@ export default class igcRatingComponent extends SizableMixin(
@property({ type: Number })
public length = 5;

/** The unfilled symbol/icon to use */
/**
* The unfilled symbol/icon to use.
* Additionally it accepts a callback function which accepts the current position
* index so the symbol can be resolved per position.
*/
@property()
public icon = 'dollar-circled';
public icon: string | ((index: number) => string) = 'dollar-circled';

/** The filled symbol/icon to use */
/**
* The filled symbol/icon to use.
* Additionally it accepts a callback function which accepts the current position
* index so the symbol can be resolved per position.
*/
@property()
public filledIcon = 'apple';
public filledIcon: string | ((index: number) => string) = 'apple';

/** The name attribute of the control */
@property()
Expand Down Expand Up @@ -127,14 +136,20 @@ export default class igcRatingComponent extends SizableMixin(

protected handleClick(event: MouseEvent) {
if (this.isIconElement(event.target) && !(this.readonly || this.disabled)) {
this.value = [...this.icons].indexOf(event.target) + 1;
const index = [...this.icons].indexOf(event.target) + 1;
if (index === this.value) {
this.value = 0;
} else {
this.value = index;
}
this.emitEvent('igcChange', { detail: this.value });
}
}

protected handleMouseOver(event: MouseEvent) {
if (this.isIconElement(event.target) && !(this.readonly || this.disabled)) {
this.hoverValue = [...this.icons].indexOf(event.target) + 1;
this.emitEvent('igcHover', { detail: this.hoverValue });
}
}

Expand Down Expand Up @@ -165,7 +180,14 @@ export default class igcRatingComponent extends SizableMixin(

private bindValue(index: number) {
const value = this.hoverState ? this.hoverValue : this.value;
return index < value ? this.filledIcon : this.icon;
return index < value
? this.renderIcon(index, 'rated')
: this.renderIcon(index, 'not-rated');
}

private renderIcon(index: number, state: 'rated' | 'not-rated') {
const symbol = state === 'rated' ? this.filledIcon : this.icon;
return typeof symbol === 'function' ? symbol(index) : symbol;
}

private isIconElement(el: any): el is IgcIconComponent {
Expand Down
67 changes: 52 additions & 15 deletions stories/rating.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,23 @@ const metadata = {
},
icon: {
type: 'string',
description: 'The unfilled symbol/icon to use',
description:
'The unfilled symbol/icon to use.\nAdditionally it accepts a callback function which accepts the current position\nindex so the symbol can be resolved per position.',
control: 'text',
defaultValue: 'dollar-circled',
},
filledIcon: {
type: 'string',
description: 'The filled symbol/icon to use',
description:
'The filled symbol/icon to use.\nAdditionally it accepts a callback function which accepts the current position\nindex so the symbol can be resolved per position.',
control: 'text',
defaultValue: 'apple',
},
name: {
type: 'string',
description: 'The name attribute of the control',
control: 'text',
},
label: {
type: 'string',
description: 'The label of the control.',
Expand Down Expand Up @@ -70,6 +77,7 @@ interface ArgTypes {
length: number;
icon: string;
filledIcon: string;
name: string;
label: string;
value: number;
disabled: boolean;
Expand All @@ -79,6 +87,12 @@ interface ArgTypes {
}
// endregion

(metadata as any).parameters = {
actions: {
handles: ['igcChange', 'igcHover'],
},
};

const Template: Story<ArgTypes, Context> = (
{
size,
Expand All @@ -93,19 +107,42 @@ const Template: Story<ArgTypes, Context> = (
}: ArgTypes,
{ globals: { direction } }: Context
) => {
return html` <igc-rating
label=${ifDefined(label)}
dir=${ifDefined(direction)}
?disabled=${disabled}
?hover=${hover}
?readonly=${readonly}
.icon=${icon}
.filledIcon=${filledIcon}
.length=${length}
.value=${value}
.size=${size}
>
</igc-rating>`;
const unfilled = (index: number) => {
switch (index) {
case 0:
return 'coronavirus';
case 1:
return 'atm';
case 2:
return 'biking';
case 3:
return 'award';
case 4:
return 'bacteria';
default:
return 'dollar-circled';
}
};

return html`
<igc-rating
label=${ifDefined(label)}
dir=${ifDefined(direction)}
?disabled=${disabled}
?hover=${hover}
?readonly=${readonly}
.icon=${icon || unfilled}
.filledIcon=${filledIcon}
.length=${length}
.value=${value}
.size=${size}
>
</igc-rating>
<h5>
If you set an empty string for the <em>icon</em> attribute the callback
for unrated symbols will take over.
</h5>
`;
};

export const Basic = Template.bind({});

0 comments on commit 8951170

Please sign in to comment.