-
Notifications
You must be signed in to change notification settings - Fork 1
feat(Flag): add Flag component (#DS-1405) #417
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
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
c2fecc8
feat(Flag): add Flag component (#DS-1405)
NikGurev ef0dc12
chore: after review
NikGurev c562a07
chore: after review
NikGurev 595fbea
chore: upd documentation
NikGurev da6e544
chore: after review
NikGurev 08df406
chore: update golden file
NikGurev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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,130 @@ | ||
| import { | ||
| Meta, | ||
| Story, | ||
| Props, | ||
| Status, | ||
| } from '../../../../../.storybook/components'; | ||
|
|
||
| import * as Stories from './Flag.stories'; | ||
|
|
||
| <Meta of={Stories} /> | ||
|
|
||
| # Flag | ||
|
|
||
| <Status variant="experimental" /> | ||
|
|
||
| Flag shows a small country flag inside a product UI (inline with text, in lists, options, selects). | ||
|
|
||
| It is a thin presentational wrapper: it decorates a flag graphic you provide and does **not** ship | ||
| or resolve any flag data. Flag graphics are expected to come from | ||
| [`country-flag-icons`](https://www.npmjs.com/package/country-flag-icons) (full ISO 3166-1, redrawn | ||
| for small sizes) or any custom source. | ||
|
|
||
| ## Import | ||
|
|
||
| ```tsx | ||
| import { Flag } from '@koobiq/react-components'; | ||
| ``` | ||
|
|
||
| ## Usage | ||
|
|
||
| Provide the flag graphic as `children`. The recommended path is a ready-made React flag component | ||
| from [`country-flag-icons`](https://www.npmjs.com/package/country-flag-icons) — no extra requests, no | ||
| base64, and it renders an inline `<svg>` under the hood. A raw inline `<svg>` or an `<img>` also | ||
| works. | ||
|
|
||
| ```tsx | ||
| import { DE } from 'country-flag-icons/react/3x2'; | ||
|
|
||
| <Flag role="img" aria-label="Germany"> | ||
| <DE /> | ||
| </Flag>; | ||
| ``` | ||
|
|
||
| <Story of={Stories.Base} /> | ||
|
|
||
| ## Props | ||
|
|
||
| <Props of={Stories.Base} /> | ||
|
|
||
| ## Shape | ||
|
|
||
| The `shape` prop controls the corner treatment: `rectangle` (default) or `circle`. `circle` clips the | ||
| flag to a circle and defaults its ratio to `1 / 1`. For a sharp-cornered square, use | ||
| `aspectRatio="1 / 1"` (see below). | ||
|
|
||
| <Story of={Stories.Shape} /> | ||
|
|
||
| ## Shadow | ||
|
|
||
| A thin inset hairline separates the flag from the background. It is on by default and adapts to the theme (dark hairline in the light theme, lighter in the dark theme). | ||
|
|
||
| <Story of={Stories.Shadow} /> | ||
|
|
||
| ## Sizing | ||
|
|
||
| By default the flag's height is `1em`, so it tracks the surrounding text — inline, in lists, and in | ||
| options you set nothing. For an explicit height use the `size` prop (a number is pixels, a string is | ||
| any CSS length), or override the `--kbq-flag-size` variable to size a whole context at once. | ||
|
|
||
| ```tsx | ||
| <Flag size={24} /> // 24px | ||
| <Flag size="2rem" /> // any CSS length | ||
| ``` | ||
|
|
||
| <Story of={Stories.Sizes} /> | ||
|
|
||
| ## Aspect ratio | ||
|
|
||
| Set the box ratio with the `aspectRatio` prop. | ||
|
|
||
| <Story of={Stories.AspectRatio} /> | ||
|
|
||
| ## Missing flag | ||
|
|
||
| The library ships no flag data, so guard the country code (e.g. with `hasFlag` from | ||
| `country-flag-icons`) and render a neutral placeholder when a flag is missing — | ||
| the layout never breaks and no broken graphic is shown. | ||
|
|
||
| <Story of={Stories.Fallback} /> | ||
|
|
||
| ## Accessibility | ||
|
|
||
| A flag must never be a silent, meaning-bearing graphic. Flag adds no ARIA semantics on its own — | ||
| supply them with standard attributes: | ||
|
|
||
| - When the flag carries meaning and has **no adjacent text**, pass a `aria-label` and `role="img"`. | ||
| - When adjacent visible text already names the country, mark the flag with `aria-hidden="true"`. It is hidden from | ||
| assistive technology to avoid duplication. | ||
|
|
||
| <Story of={Stories.Accessibility} /> | ||
|
|
||
| ## Flag is not a language | ||
|
|
||
| A flag represents a country, not a language. Use a globe icon (not a flag) for language selection. | ||
|
|
||
| <Story of={Stories.NotForLanguage} /> | ||
|
|
||
| ## Custom look | ||
|
|
||
| Stylized, decorative treatments (rounded corners, drop shadow, "folds" gradient) are your own CSS | ||
| plus the exposed variables — not a library prop. Round the corners with `--kbq-flag-border-radius` | ||
| and add a shadow/gradient in your own styles. | ||
|
|
||
| <Story of={Stories.Custom} /> | ||
|
|
||
| ## Flags in a Select | ||
|
|
||
| <Story of={Stories.FlagsInSelect} /> | ||
|
|
||
| ## CSS variables | ||
|
|
||
| Everything visual that isn't a discrete state is an overridable CSS variable: | ||
|
|
||
| | Variable | Default | Purpose | | ||
| | ----------------------------- | --------------------------------------- | ----------------------------------------------------- | | ||
| | `--kbq-flag-size` | `1em` | Flag height (also settable via the `size` prop). | | ||
| | `--kbq-flag-aspect-ratio` | `3 / 2` | Box ratio (also settable via the `aspectRatio` prop). | | ||
| | `--kbq-flag-border-radius` | `0` | Corner radius (rounded / stylized look). | | ||
| | `--kbq-flag-shadow-color` | `var(--kbq-line-contrast-fade)` | Inset hairline color; theme-adaptive. | | ||
| | `--kbq-flag-empty-background` | `var(--kbq-states-background-disabled)` | Placeholder fill. | | ||
This file contains hidden or 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,47 @@ | ||
| .base { | ||
| /* Public, overridable knobs (see the component docs). */ | ||
| --kbq-flag-aspect-ratio: 3 / 2; | ||
|
KamilEmeleev marked this conversation as resolved.
|
||
| --kbq-flag-border-radius: 0; | ||
| --kbq-flag-shadow-color: var(--kbq-line-contrast-fade); | ||
| --kbq-flag-empty-background: var(--kbq-states-background-disabled); | ||
|
|
||
| display: inline-block; | ||
| position: relative; | ||
| overflow: hidden; | ||
| vertical-align: middle; | ||
| block-size: var(--kbq-flag-size, 1em); | ||
| aspect-ratio: var(--kbq-flag-aspect-ratio); | ||
| border-radius: var(--kbq-flag-border-radius); | ||
| } | ||
|
|
||
| /* The projected flag graphic fills the box (descendant selector supports wrapped graphics). */ | ||
| .base :is(svg, img) { | ||
| display: block; | ||
| inline-size: 100%; | ||
| block-size: 100%; | ||
| object-fit: cover; | ||
| } | ||
|
|
||
| /* Inset hairline separating the flag from the background (shown by default). */ | ||
| .base::after { | ||
| content: ''; | ||
| position: absolute; | ||
| inset: 0; | ||
| border-radius: inherit; | ||
| border: 1px solid var(--kbq-flag-shadow-color); | ||
| pointer-events: none; | ||
| } | ||
|
|
||
| .base[data-hide-shadow]::after { | ||
| content: none; | ||
| } | ||
|
|
||
| /* `circle` only clips the corners; its 1:1 ratio comes from the component. */ | ||
| .base[data-shape='circle'] { | ||
| border-radius: 50%; | ||
| } | ||
|
|
||
| /* No projected graphic → neutral placeholder (e.g. unknown/invalid country). */ | ||
| .base:empty { | ||
| background-color: var(--kbq-flag-empty-background); | ||
| } | ||
21 changes: 21 additions & 0 deletions
21
packages/components/src/components/Flag/Flag.stories.module.css
This file contains hidden or 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,21 @@ | ||
| /* Gradient imitating folds, blended over the flag (stylized docs example). */ | ||
| .stylized::before { | ||
| content: ''; | ||
| position: absolute; | ||
| inset: 0; | ||
| z-index: 1; | ||
| border-radius: inherit; | ||
| background: linear-gradient( | ||
| 240.64deg, | ||
| rgb(255 255 255 / 30%) 0%, | ||
| rgb(0 0 0 / 27%) 26.27%, | ||
| rgb(255 255 255 / 26%) 37%, | ||
| rgb(0 0 0 / 55%) 48.7%, | ||
| rgb(0 0 0 / 24%) 59.44%, | ||
| rgb(255 255 255 / 30%) 73.64%, | ||
| rgb(39 39 39 / 22%) 90.15%, | ||
| rgb(0 0 0 / 20%) 100% | ||
| ); | ||
| mix-blend-mode: overlay; | ||
| pointer-events: none; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.