PlRating
별 한 줄로 표현한 5점 만점의 점수입니다. 조작 가능한 rating 아래에는 진짜 라디오 그룹이 있습니다: 탭 정지 하나, 화살표 키, 그리고 폼 전송에 실리는 값.
import { PlRating } from 'plass-ui';
<PlRating value={score} onValueChange={setScore} />;import 'package:plass_ui/plass_ui.dart';
PlRating(
value: score,
onChanged: (double next) => setState(() => score = next),
);Props
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| value | number | — | 점수. onValueChange와 함께 controlled로 씁니다 |
| defaultValue | number | 0 | uncontrolled일 때 시작 점수 |
| onValueChange | (value: number) => void | — | 새 점수로 호출됩니다. 지워지면 0입니다 |
| count | number | 5 | 별의 개수, 곧 최고 점수 |
| precision | number | 1 | 고를 수 있는 가장 작은 단위. 0.5는 반 별. 그려지는 값은 제한하지 않습니다 |
| icon · emptyIcon | ReactNode | — | 채워진 별과 빈 별의 글리프. 같은 모양이어야 합니다 |
| clearable | boolean | true | 이미 고른 점수를 다시 고르면 0으로 지워집니다 |
| readOnly | boolean | false | 점수를 보여 주기만 합니다. input이 사라지고 이미지 하나가 됩니다 |
| disabled | boolean | false | 사용할 수 없습니다. 줄에서 빛이 꺼집니다 |
| name | string | — | 폼 전송에서 값을 식별합니다 |
| required | boolean | false | 별을 고르기 전까지 폼이 전송되지 않습니다 |
| size공통 | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | 별 하나의 높이. 독립 글리프 사다리입니다 |
| color공통 | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'warning' | 의미론적 색 역할. 기본이 warning인 것은 별에 기대되는 색이기 때문입니다 |
| label | string | 'Rating' | 컨트롤 전체의 접근 가능한 이름 |
| valueLabel | (value: number, count: number) => string | `{value} out of {count}` | 한 선택지의, 그리고 읽기 전용일 때 컨트롤 전체의 접근 가능한 이름 |
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| value * | double | — | 점수. 0은 평가 없음입니다. controlled입니다 — defaultValue는 없습니다 |
| onChanged | ValueChanged<double>? | — | 새 점수로 호출됩니다. 주지 않으면 점수가 그대로 굳습니다 |
| count | int | 5 | 별의 개수, 곧 최고 점수 |
| precision | double | 1 | 고를 수 있는 가장 작은 단위. 0.5는 반 별. 그려지는 값은 제한하지 않습니다 |
| icon · emptyIcon | Widget? | — | 채워진 별과 빈 별의 글리프. 같은 모양이어야 합니다 |
| clearable | bool | true | 이미 고른 점수를 다시 고르면 0으로 지워집니다 |
| readOnly | bool | false | 점수를 보여 주기만 합니다. input이 사라지고 이미지 하나가 됩니다 |
| disabled | bool | false | 사용할 수 없습니다. 줄에서 빛이 꺼집니다 |
| size공통 | PlassSize | PlassSize.md | 별 하나의 높이. 독립 글리프 사다리입니다 |
| color공통 | PlassColor | PlassColor.warning | 의미론적 색 역할. 기본이 warning인 것은 별에 기대되는 색이기 때문입니다 |
| label | String | 'Rating' | 컨트롤 전체의 접근 가능한 이름 |
| valueLabel | PlRatingValueLabel | PlRating.defaultValueLabel | 한 선택지의, 그리고 읽기 전용일 때 컨트롤 전체의 접근 가능한 이름 |
| focusNode · autofocus | FocusNode? · bool | — | 포커스를 밖에서 제어하거나, 트리에 들어가면서 포커스를 가져갑니다 |
네이티브 <div> 속성은 그대로 전달됩니다. color는 위 표의 color와 충돌해서, onChange는 이 컴포넌트가 onValueChange로 쓰기 때문에 제외됩니다.
rating은 controlled입니다. value를 받고 그것을 대체해야 할 값을 보고합니다. 이 패키지 어디에도 defaultValue는 없습니다. Flutter 자체 컨트롤이 그렇게 동작하기 때문입니다.
variant도 elevation도 없습니다. 별은 페이지 위의 표식이지 표면이 아닙니다. 라이브러리 전체에서 공유 축이 뜻하는 바는 prop 규칙에 있습니다.
Examples
precision
고를 수 있는 가장 작은 단위입니다. 별 하나에 대한 분수로 씁니다. 0.5는 반 별, 1은 온 별입니다.
이것은 독자가 고를 수 있는 범위만 정하고 그 외에는 아무것도 하지 않습니다. value가 4.3이면 어떤 precision에서도 별 넷과 3분의 1로 그려집니다. 평균은 선택이 아니고, 그것을 가장 가까운 반 별로 반올림하는 것은 받은 수와 다른 수를 보고하는 일이기 때문입니다.
import { PlRating, PlTypography } from 'plass-ui';
export default function RatingPrecision() {
return (
<div className="flex flex-col gap-3">
{[1, 0.5, 0.25].map((precision) => (
<div key={precision} className="flex items-center gap-3">
<PlRating precision={precision} defaultValue={3} />
<PlTypography level="caption">precision={precision}</PlTypography>
</div>
))}
</div>
);
}import 'package:flutter/widgets.dart';
import 'package:plass_ui/plass_ui.dart';
class RatingPrecision extends StatefulWidget {
const RatingPrecision({super.key});
@override
State<RatingPrecision> createState() => _RatingPrecisionState();
}
class _RatingPrecisionState extends State<RatingPrecision> {
final Map<double, double> _scores = <double, double>{1: 3, 0.5: 3, 0.25: 3};
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 12,
children: <Widget>[
for (final double precision in _scores.keys)
Row(
mainAxisSize: MainAxisSize.min,
spacing: 12,
children: <Widget>[
PlRating(
value: _scores[precision]!,
precision: precision,
onChanged: (double next) => setState(() => _scores[precision] = next),
),
PlTypography('precision: $precision', level: PlTypographyLevel.caption),
],
),
],
);
}
}readOnly
상품의 평균 점수, 또는 다른 사람이 남긴 평가입니다.
같은 옷을 입은 다른 컴포넌트입니다. input도 없고, 라디오 그룹도 없고, 점수를 문장으로 들고 있는 role="img"이미지 시맨틱 노드 하나만 있습니다. 포커스 가능한 라디오 스무 개를 그대로 들고 있는 별 표시는, 수 하나를 보고할 뿐인 페이지에 탭 정지를 스무 개 놓는 일입니다.
이것은 라이브러리에서 채도를 빼지 않는 유일한 readOnly이기도 합니다. 붙들려 있는 컨트롤이 아니라(남은 컨트롤이 없습니다) 회색 별 한 줄은 점수 자체를 쓸 수 없다는 말이 되어 버립니다.
import { PlRating, PlTypography } from 'plass-ui';
export default function RatingAverage() {
return (
<div className="flex flex-col gap-3">
{[4.3, 2.5, 0].map((score) => (
<div key={score} className="flex items-center gap-3">
<PlRating readOnly value={score} />
<PlTypography level="caption">value={score}</PlTypography>
</div>
))}
</div>
);
}import 'package:flutter/widgets.dart';
import 'package:plass_ui/plass_ui.dart';
class RatingAverage extends StatelessWidget {
const RatingAverage({super.key});
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 12,
children: <Widget>[
for (final double score in <double>[4.3, 2.5, 0])
Row(
mainAxisSize: MainAxisSize.min,
spacing: 12,
children: <Widget>[
PlRating(value: score, readOnly: true),
PlTypography('value: $score', level: PlTypographyLevel.caption),
],
),
],
);
}
}분수
채워진 별을 빈 별 위에 얹고 너비의 비율만큼 잘라 냅니다. 아무것도 변형하지 않고 어떤 글리프도 축소하지 않아서, 반 별은 바로 옆 별의 정확히 왼쪽 절반입니다. 부분적인 모양이 존재 이유인 컴포넌트에서도 하우스의 no-transform 규칙이 그대로 성립하는 지점입니다.
잘라내는 기준은 시작하는 쪽 가장자리입니다. 그래서 RTL에서는 아무 지시 없이도 오른쪽부터 채워집니다.
icon과 emptyIcon
둘 다 주거나, 둘 다 주지 않습니다. 두 그림은 하나 위에 하나를 얹고 위쪽을 잘라내므로, 채워진 하트를 외곽선 별 위에 얹으면 안쪽과 맞지 않는 테두리로 보입니다.
import { PlRating } from 'plass-ui';
const HeartFilled = () => (
<svg viewBox="0 0 16 16" fill="currentColor">
<path d="M8 13.7 1.9 8.1a3.4 3.4 0 0 1 4.8-4.8L8 4.6l1.3-1.3a3.4 3.4 0 1 1 4.8 4.8Z" />
</svg>
);
const HeartOutline = () => (
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.3">
<path d="M8 13.7 1.9 8.1a3.4 3.4 0 0 1 4.8-4.8L8 4.6l1.3-1.3a3.4 3.4 0 1 1 4.8 4.8Z" />
</svg>
);
export default function RatingIcons() {
return (
<PlRating color="danger" defaultValue={3} icon={<HeartFilled />} emptyIcon={<HeartOutline />} />
);
}import 'package:flutter/widgets.dart';
import 'package:plass_ui/plass_ui.dart';
import 'package:plass_ui_example/demos/glyphs.dart';
class RatingIcons extends StatefulWidget {
const RatingIcons({super.key});
@override
State<RatingIcons> createState() => _RatingIconsState();
}
class _RatingIconsState extends State<RatingIcons> {
double _score = 3;
@override
Widget build(BuildContext context) {
return PlRating(
value: _score,
color: PlassColor.danger,
// The same drawing twice — the two are laid one over the other and the
// top one is cropped, so a filled heart over an outlined star would show
// as a rim that does not line up with what is inside it.
icon: const HeartGlyph(),
emptyIcon: const HeartGlyph(),
onChanged: (double next) => setState(() => _score = next),
);
}
}size
독립 글리프 사다리입니다. PlIcon이 쓰는 것과 같습니다. 별은 컨트롤이 아니라 내용이기 때문입니다. 자기가 앉은 줄이 아니라 옆에 있는 글자에 대해 재어집니다.
import { PlRating } from 'plass-ui';
export default function RatingSizes() {
return (
<div className="flex flex-col items-start gap-3">
{(['xs', 'sm', 'md', 'lg', 'xl'] as const).map((size) => (
<PlRating key={size} size={size} defaultValue={4} />
))}
</div>
);
}import 'package:flutter/widgets.dart';
import 'package:plass_ui/plass_ui.dart';
class RatingSizes extends StatelessWidget {
const RatingSizes({super.key});
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 12,
children: <Widget>[
for (final PlassSize size in PlassSize.values)
PlRating(value: 4, size: size, readOnly: true),
],
);
}
}color
기본값은 나머지 전부가 쓰는 primary가 아니라 warning입니다. 별에 기대되는 그 호박색. 라이브러리에서 컴포넌트의 기본 색이 그것이 무엇을 뜻하는지가 아니라 그것이 무엇인지로 정해지는 유일한 자리입니다.
import { PlRating } from 'plass-ui';
export default function RatingColors() {
return (
<div className="flex flex-col items-start gap-3">
{(['warning', 'primary', 'danger', 'success'] as const).map((color) => (
<PlRating key={color} color={color} defaultValue={4} />
))}
</div>
);
}import 'package:flutter/widgets.dart';
import 'package:plass_ui/plass_ui.dart';
class RatingColors extends StatelessWidget {
const RatingColors({super.key});
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 12,
children: <Widget>[
for (final PlassColor color in <PlassColor>[
PlassColor.warning,
PlassColor.primary,
PlassColor.danger,
PlassColor.success,
])
PlRating(value: 4, color: color, readOnly: true),
],
);
}
}clearable과 disabled
이미 고른 점수를 다시 고르면 0으로 지워집니다. 한 번 남긴 평가를 되돌리는 유일한 방법입니다. 점수가 필수인 곳에서는 꺼 두세요.
disabled는 하우스의 처리 그대로입니다. 줄에서 빛이 꺼지고 페이지가 비쳐 보이며 색 가족은 남습니다. 회색 줄은 같은 상태에 대한 두 번째 어휘가 됩니다.
import { PlRating, PlTypography } from 'plass-ui';
export default function RatingStates() {
return (
<div className="flex flex-col gap-3">
<div className="flex items-center gap-3">
<PlRating defaultValue={3} />
<PlTypography level="caption">interactive</PlTypography>
</div>
<div className="flex items-center gap-3">
<PlRating readOnly value={3.5} />
<PlTypography level="caption">readOnly</PlTypography>
</div>
<div className="flex items-center gap-3">
<PlRating disabled value={3} />
<PlTypography level="caption">disabled</PlTypography>
</div>
</div>
);
}import 'package:flutter/widgets.dart';
import 'package:plass_ui/plass_ui.dart';
class RatingStates extends StatefulWidget {
const RatingStates({super.key});
@override
State<RatingStates> createState() => _RatingStatesState();
}
class _RatingStatesState extends State<RatingStates> {
double _score = 3;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 12,
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.min,
spacing: 12,
children: <Widget>[
PlRating(value: _score, onChanged: (double next) => setState(() => _score = next)),
const PlTypography('interactive', level: PlTypographyLevel.caption),
],
),
const Row(
mainAxisSize: MainAxisSize.min,
spacing: 12,
children: <Widget>[
PlRating(value: 3.5, readOnly: true),
PlTypography('readOnly', level: PlTypographyLevel.caption),
],
),
const Row(
mainAxisSize: MainAxisSize.min,
spacing: 12,
children: <Widget>[
PlRating(value: 3, disabled: true),
PlTypography('disabled', level: PlTypographyLevel.caption),
],
),
],
);
}
}Accessibility
- 조작 가능한 rating은 라디오 그룹입니다. 점수는 "이 중 정확히 하나"이기 때문입니다. 줄 전체에 탭 정지 하나, 그 안에서 화살표 키, 선택된 점수의 표시, 그리고 폼 전송에 실리는 값. 버튼 한 줄이었다면 그중 아무것도 없었을 것들입니다.
- 모든 선택지는 그것이 나타내는 점수로 이름이 붙습니다 (
3 out of 5). 다른 언어는valueLabel에서 자기 문구를 정합니다. 여기서 화면에 그려지는 것은 없습니다. - 읽기 전용 rating은 라디오를 전부 내려놓고, 점수를 이름으로 가진 이미지 하나가 됩니다.
- 글리프는 장식입니다. 안내되는 것은 그림이 아니라 문장입니다.
- input들은 시각적으로 숨겨진 상자 안의 진짜
<input type="radio">이고, 별의 각 분수 아래에 하나씩 있습니다.name은 폼과 함께 전송되고,required는 별을 고르기 전까지 전송을 막습니다. - 지우기는
change가 아니라click에 실립니다. 이미 체크된 라디오를 클릭하면 클릭만 발생하고 change는 전혀 발생하지 않는데, 바로 그 클릭이 여기서 듣고 있는 동작이기 때문입니다.
- 고를 수 있는 모든 분수는 각자의 시맨틱 노드이고, 상호 배타적인 집합의 하나로 표시되며 자기가 나타내는 점수를 이름으로 들고 있습니다. 스크린 리더가 직접 실행할 수 있습니다.
- 줄 전체가 탭 정지 하나이고, 화살표 키가
precision한 단계씩 점수를 옮깁니다. React 빌드가 라디오 그룹에서 공짜로 얻는 것을 여기서는 직접 묶었습니다. Home은 지우고 End는 끝까지 올립니다. 화살표는 쓰기 방향을 따르므로 RTL에서는 반대로 움직입니다. - 단축키는 상속이 아니라 줄에 직접 선언되어 있습니다. 그래서 맨
WidgetsApp안에서도, 위에 앱 위젯이 하나도 없어도 똑같이 동작합니다.
React 빌드와 다른 점
| React | Flutter | 이유 |
|---|---|---|
value / defaultValue / onValueChange | value / onChanged | Flutter 자체 컨트롤이 controlled이고, 콜백 이름도 그쪽 이름입니다. |
숨겨진 <input>의 라디오 그룹 | 시맨틱 노드와 탭 정지 하나 | 전송할 폼도, 키보드를 물려받을 네이티브 라디오도 없어서 화살표를 줄에 직접 묶었습니다. |
name, required | — | 둘 다 HTML 폼 전송에 관한 것이고, Flutter에는 대응물이 없습니다. |
| 별마다 포커스 링 | 줄 하나에 링 하나 | 여기서는 줄이 포커스 노드 하나라서, 링은 실제로 포커스를 쥔 것을 두릅니다. |
className, style | — | 전달할 클래스 목록도 style 속성도 없습니다. |