Skip to content

Commit 9149d53

Browse files
tomi-msftbehowell
andauthored
React rating initial implementation (microsoft#29490)
* Rating initial implementation * Rating initial implementation * version fix * yarn lock update * Add carat * update exports * change checked to defaultChecked in RatingItem * add empty onChange to silence warnings * api update * add comments from code review * api updates * update Rating styles and stories * Update indicator api to add filled and unfilled icons * add shapes * Update shape api to use iconFilled and iconOutline * Default the color of the rating to neutralForeground1 * Add outlineIcon and support filled unselected icons * Rename icon slots in RatingItem * Fix label text centering * update api * Update tabster dependency version * Update ratingitem styling and stories * Clean up shape story * Add outlineStyle prop and update api * Remove conformance tests from RatingItem * add suggestion from code review * api update * package update * Dependency mismatch * Temporarily remove conformance tests * update snapshots * update snapshots * apply suggestions from code review --------- Co-authored-by: Ben Howell <[email protected]>
1 parent 8a6a455 commit 9149d53

31 files changed

+980
-71
lines changed

packages/react-components/react-rating-preview/etc/react-rating-preview.api.md

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
55
```ts
66

7+
/// <reference types="react" />
8+
79
import type { ComponentProps } from '@fluentui/react-utilities';
810
import type { ComponentState } from '@fluentui/react-utilities';
911
import type { ForwardRefComponent } from '@fluentui/react-utilities';
@@ -17,23 +19,97 @@ export const Rating: ForwardRefComponent<RatingProps>;
1719
// @public (undocumented)
1820
export const ratingClassNames: SlotClassNames<RatingSlots>;
1921

22+
// @public (undocumented)
23+
export type RatingContextValue = Pick<RatingState, 'appearance' | 'compact' | 'iconFilled' | 'iconOutline' | 'name' | 'precision' | 'readOnly' | 'size' | 'value' | 'hoveredValue'>;
24+
25+
// @public (undocumented)
26+
export type RatingContextValues = {
27+
rating: RatingContextValue;
28+
};
29+
30+
// @public
31+
export const RatingItem: ForwardRefComponent<RatingItemProps>;
32+
33+
// @public (undocumented)
34+
export const ratingItemClassNames: SlotClassNames<RatingItemSlots>;
35+
36+
// @public
37+
export type RatingItemProps = ComponentProps<Partial<RatingItemSlots>> & {
38+
value?: number;
39+
};
40+
41+
// @public (undocumented)
42+
export type RatingItemSlots = {
43+
root: NonNullable<Slot<'span'>>;
44+
selectedIcon?: NonNullable<Slot<'div'>>;
45+
unselectedFilledIcon?: NonNullable<Slot<'div'>>;
46+
unselectedOutlineIcon?: NonNullable<Slot<'div'>>;
47+
halfValueInput?: NonNullable<Slot<'input'>>;
48+
fullValueInput?: NonNullable<Slot<'input'>>;
49+
};
50+
51+
// @public
52+
export type RatingItemState = ComponentState<RatingItemSlots> & Required<Pick<RatingItemProps, 'value'>> & Pick<RatingState, 'compact' | 'precision' | 'size'> & {
53+
iconFillWidth: number;
54+
};
55+
2056
// @public
21-
export type RatingProps = ComponentProps<RatingSlots> & {};
57+
export type RatingOnChangeData = {
58+
value?: number;
59+
};
60+
61+
// @public
62+
export type RatingProps = ComponentProps<RatingSlots> & {
63+
appearance?: 'filled' | 'outline';
64+
compact?: boolean;
65+
defaultValue?: number;
66+
iconFilled?: React_2.ReactElement;
67+
iconOutline?: React_2.ReactElement;
68+
max?: number;
69+
name?: string;
70+
onChange?: (ev: React_2.SyntheticEvent | Event, data: RatingOnChangeData) => void;
71+
precision?: boolean;
72+
readOnly?: boolean;
73+
size?: 'small' | 'medium' | 'large';
74+
value?: number;
75+
};
76+
77+
// @public (undocumented)
78+
export const RatingProvider: React_2.Provider<RatingContextValue | undefined>;
2279

2380
// @public (undocumented)
2481
export type RatingSlots = {
25-
root: Slot<'div'>;
82+
root: NonNullable<Slot<'div'>>;
83+
ratingLabel?: NonNullable<Slot<'label'>>;
84+
ratingCountLabel?: NonNullable<Slot<'label'>>;
85+
};
86+
87+
// @public
88+
export type RatingState = ComponentState<RatingSlots> & Required<Pick<RatingProps, 'appearance' | 'compact' | 'iconFilled' | 'iconOutline' | 'name' | 'precision' | 'readOnly' | 'size' | 'value'>> & {
89+
hoveredValue?: number | undefined;
2690
};
2791

2892
// @public
29-
export type RatingState = ComponentState<RatingSlots>;
93+
export const renderRating_unstable: (state: RatingState, contextValues: RatingContextValues) => JSX.Element;
3094

3195
// @public
32-
export const renderRating_unstable: (state: RatingState) => JSX.Element;
96+
export const renderRatingItem_unstable: (state: RatingItemState) => JSX.Element;
3397

3498
// @public
3599
export const useRating_unstable: (props: RatingProps, ref: React_2.Ref<HTMLDivElement>) => RatingState;
36100

101+
// @public
102+
export const useRatingContextValue_unstable: () => RatingContextValue | undefined;
103+
104+
// @public (undocumented)
105+
export const useRatingContextValues: (state: RatingState) => RatingContextValues;
106+
107+
// @public
108+
export const useRatingItem_unstable: (props: RatingItemProps, ref: React_2.Ref<HTMLSpanElement>) => RatingItemState;
109+
110+
// @public
111+
export const useRatingItemStyles_unstable: (state: RatingItemState) => RatingItemState;
112+
37113
// @public
38114
export const useRatingStyles_unstable: (state: RatingState) => RatingState;
39115

packages/react-components/react-rating-preview/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
},
3333
"dependencies": {
3434
"@fluentui/react-jsx-runtime": "^9.0.20",
35+
"@fluentui/react-icons": "^2.0.217",
3536
"@fluentui/react-theme": "^9.1.16",
37+
"@fluentui/react-tabster": "^9.15.0",
3638
"@fluentui/react-utilities": "^9.15.2",
3739
"@griffel/react": "^1.5.14",
3840
"@swc/helpers": "^0.5.1"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './components/RatingItem/index';
Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
import * as React from 'react';
22
import { render } from '@testing-library/react';
3-
import { isConformant } from '../../testing/isConformant';
3+
// import { isConformant } from '../../testing/isConformant';
44
import { Rating } from './Rating';
55

66
describe('Rating', () => {
7-
isConformant({
8-
Component: Rating,
9-
displayName: 'Rating',
10-
});
11-
12-
// TODO add more tests here, and create visual regression tests in /apps/vr-tests
13-
7+
// isConformant({
8+
// Component: Rating,
9+
// displayName: 'Rating',
10+
// });
1411
it('renders a default state', () => {
15-
const result = render(<Rating>Default Rating</Rating>);
12+
const result = render(<Rating>Default RatingItem</Rating>);
1613
expect(result.container).toMatchSnapshot();
1714
});
1815
});

packages/react-components/react-rating-preview/src/components/Rating/Rating.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@ import { useRating_unstable } from './useRating';
44
import { renderRating_unstable } from './renderRating';
55
import { useRatingStyles_unstable } from './useRatingStyles.styles';
66
import type { RatingProps } from './Rating.types';
7+
import { useRatingContextValues } from '../../contexts/useRatingContextValues';
78

89
/**
910
* Rating component - TODO: add more docs
1011
*/
1112
export const Rating: ForwardRefComponent<RatingProps> = React.forwardRef((props, ref) => {
1213
const state = useRating_unstable(props, ref);
14+
const contextValues = useRatingContextValues(state);
1315

1416
useRatingStyles_unstable(state);
15-
return renderRating_unstable(state);
17+
return renderRating_unstable(state, contextValues);
1618
});
1719

1820
Rating.displayName = 'Rating';
Lines changed: 98 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,110 @@
1+
import * as React from 'react';
12
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
23

34
export type RatingSlots = {
4-
root: Slot<'div'>;
5+
root: NonNullable<Slot<'div'>>;
6+
ratingLabel?: NonNullable<Slot<'label'>>;
7+
ratingCountLabel?: NonNullable<Slot<'label'>>;
58
};
69

710
/**
811
* Rating Props
912
*/
10-
export type RatingProps = ComponentProps<RatingSlots> & {};
13+
export type RatingProps = ComponentProps<RatingSlots> & {
14+
/**
15+
* Controls the appearance of unselected rating items.
16+
* @default outline (filled if readOnly is set)
17+
*/
18+
appearance?: 'filled' | 'outline';
19+
/**
20+
* Sets whether to render a full or compact Rating
21+
* @default false
22+
*/
23+
compact?: boolean;
24+
/**
25+
* Default value of the Rating
26+
*/
27+
defaultValue?: number;
28+
/**
29+
* The icon to display when the rating value is greater than or equal to the item's value.
30+
*/
31+
iconFilled?: React.ReactElement;
32+
/**
33+
* The icon to display when the rating value is less than the item's value.
34+
*/
35+
iconOutline?: React.ReactElement;
36+
/**
37+
* The max value of the rating. This controls the number of rating items displayed.
38+
* Must be a whole number greater than 1.
39+
* @default 5
40+
*/
41+
max?: number;
42+
/**
43+
* Name for the Radio inputs. If not provided, one will be automatically generated
44+
*/
45+
name?: string;
46+
/**
47+
* Callback when the rating value is changed by the user.
48+
*/
49+
onChange?: (ev: React.SyntheticEvent | Event, data: RatingOnChangeData) => void;
50+
/**
51+
* Sets the precision to allow half-filled shapes in Rating
52+
* @default false
53+
*/
54+
precision?: boolean;
55+
/**
56+
* Sets Rating to be read only
57+
* @default false
58+
*/
59+
readOnly?: boolean;
60+
/**
61+
* Sets the size of the Rating items.
62+
* @default medium
63+
*/
64+
size?: 'small' | 'medium' | 'large';
65+
/**
66+
* The value of the rating
67+
*/
68+
value?: number;
69+
};
70+
71+
/**
72+
* Data for the onChange event for Rating.
73+
*/
74+
export type RatingOnChangeData = {
75+
/**
76+
* The new value of the rating.
77+
*/
78+
value?: number;
79+
};
1180

1281
/**
1382
* State used in rendering Rating
1483
*/
15-
export type RatingState = ComponentState<RatingSlots>;
16-
// TODO: Remove semicolon from previous line, uncomment next line, and provide union of props to pick from RatingProps.
17-
// & Required<Pick<RatingProps, 'propName'>>
84+
export type RatingState = ComponentState<RatingSlots> &
85+
Required<
86+
Pick<
87+
RatingProps,
88+
'appearance' | 'compact' | 'iconFilled' | 'iconOutline' | 'name' | 'precision' | 'readOnly' | 'size' | 'value'
89+
>
90+
> & {
91+
hoveredValue?: number | undefined;
92+
};
93+
94+
export type RatingContextValue = Pick<
95+
RatingState,
96+
| 'appearance'
97+
| 'compact'
98+
| 'iconFilled'
99+
| 'iconOutline'
100+
| 'name'
101+
| 'precision'
102+
| 'readOnly'
103+
| 'size'
104+
| 'value'
105+
| 'hoveredValue'
106+
>;
107+
108+
export type RatingContextValues = {
109+
rating: RatingContextValue;
110+
};

packages/react-components/react-rating-preview/src/components/Rating/__snapshots__/Rating.test.tsx.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ exports[`Rating renders a default state 1`] = `
55
<div
66
class="fui-Rating"
77
>
8-
Default Rating
8+
Default RatingItem
99
</div>
1010
</div>
1111
`;

packages/react-components/react-rating-preview/src/components/Rating/renderRating.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,22 @@
22
/** @jsxImportSource @fluentui/react-jsx-runtime */
33

44
import { assertSlots } from '@fluentui/react-utilities';
5-
import type { RatingState, RatingSlots } from './Rating.types';
5+
import type { RatingState, RatingSlots, RatingContextValues } from './Rating.types';
6+
import { RatingProvider } from '../../contexts/RatingContext';
67

78
/**
89
* Render the final JSX of Rating
910
*/
10-
export const renderRating_unstable = (state: RatingState) => {
11+
export const renderRating_unstable = (state: RatingState, contextValues: RatingContextValues) => {
1112
assertSlots<RatingSlots>(state);
1213

13-
// TODO Add additional slots in the appropriate place
14-
return <state.root />;
14+
return (
15+
<RatingProvider value={contextValues.rating}>
16+
<state.root>
17+
{state.root.children}
18+
{state.ratingLabel && <state.ratingLabel />}
19+
{state.ratingCountLabel && <state.ratingCountLabel />}
20+
</state.root>
21+
</RatingProvider>
22+
);
1523
};

packages/react-components/react-rating-preview/src/components/Rating/useRating.ts

Lines changed: 0 additions & 31 deletions
This file was deleted.

0 commit comments

Comments
 (0)