@joanroucoux/cairn-ui 0.7.2 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +72 -0
- package/README.md +67 -0
- package/fesm2022/joanroucoux-cairn-ui-action-bar.mjs +54 -0
- package/fesm2022/joanroucoux-cairn-ui-action-bar.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-alert.mjs +129 -0
- package/fesm2022/joanroucoux-cairn-ui-alert.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-amount.mjs +114 -0
- package/fesm2022/joanroucoux-cairn-ui-amount.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-async.mjs +282 -0
- package/fesm2022/joanroucoux-cairn-ui-async.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-avatar.mjs +71 -0
- package/fesm2022/joanroucoux-cairn-ui-avatar.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-back-link.mjs +79 -0
- package/fesm2022/joanroucoux-cairn-ui-back-link.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-badge.mjs +48 -0
- package/fesm2022/joanroucoux-cairn-ui-badge.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-button.mjs +128 -0
- package/fesm2022/joanroucoux-cairn-ui-button.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-card.mjs +146 -0
- package/fesm2022/joanroucoux-cairn-ui-card.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-choice-chips.mjs +172 -0
- package/fesm2022/joanroucoux-cairn-ui-choice-chips.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-control.mjs +18 -0
- package/fesm2022/joanroucoux-cairn-ui-control.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-delta.mjs +103 -0
- package/fesm2022/joanroucoux-cairn-ui-delta.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-dialog.mjs +626 -0
- package/fesm2022/joanroucoux-cairn-ui-dialog.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-donut.mjs +347 -0
- package/fesm2022/joanroucoux-cairn-ui-donut.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-empty.mjs +47 -0
- package/fesm2022/joanroucoux-cairn-ui-empty.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-external-link.mjs +65 -0
- package/fesm2022/joanroucoux-cairn-ui-external-link.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-fact.mjs +99 -0
- package/fesm2022/joanroucoux-cairn-ui-fact.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-field.mjs +232 -0
- package/fesm2022/joanroucoux-cairn-ui-field.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-filter-chips.mjs +89 -0
- package/fesm2022/joanroucoux-cairn-ui-filter-chips.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-input.mjs +90 -0
- package/fesm2022/joanroucoux-cairn-ui-input.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-line-chart.mjs +844 -0
- package/fesm2022/joanroucoux-cairn-ui-line-chart.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-menu.mjs +241 -0
- package/fesm2022/joanroucoux-cairn-ui-menu.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-meter.mjs +86 -0
- package/fesm2022/joanroucoux-cairn-ui-meter.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-motion.mjs +415 -0
- package/fesm2022/joanroucoux-cairn-ui-motion.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-nav-item.mjs +45 -0
- package/fesm2022/joanroucoux-cairn-ui-nav-item.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-row.mjs +164 -0
- package/fesm2022/joanroucoux-cairn-ui-row.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-segmented.mjs +165 -0
- package/fesm2022/joanroucoux-cairn-ui-segmented.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-select.mjs +69 -0
- package/fesm2022/joanroucoux-cairn-ui-select.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-skeleton.mjs +91 -0
- package/fesm2022/joanroucoux-cairn-ui-skeleton.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-stale-link.mjs +43 -0
- package/fesm2022/joanroucoux-cairn-ui-stale-link.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-stat.mjs +96 -0
- package/fesm2022/joanroucoux-cairn-ui-stat.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-switch.mjs +37 -0
- package/fesm2022/joanroucoux-cairn-ui-switch.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-tab-bar.mjs +75 -0
- package/fesm2022/joanroucoux-cairn-ui-tab-bar.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-table.mjs +321 -0
- package/fesm2022/joanroucoux-cairn-ui-table.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-toast.mjs +153 -0
- package/fesm2022/joanroucoux-cairn-ui-toast.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui.mjs +0 -5462
- package/fesm2022/joanroucoux-cairn-ui.mjs.map +1 -1
- package/package.json +141 -1
- package/types/joanroucoux-cairn-ui-action-bar.d.ts +22 -0
- package/types/joanroucoux-cairn-ui-alert.d.ts +23 -0
- package/types/joanroucoux-cairn-ui-amount.d.ts +45 -0
- package/types/joanroucoux-cairn-ui-async.d.ts +58 -0
- package/types/joanroucoux-cairn-ui-avatar.d.ts +35 -0
- package/types/joanroucoux-cairn-ui-back-link.d.ts +22 -0
- package/types/joanroucoux-cairn-ui-badge.d.ts +23 -0
- package/types/joanroucoux-cairn-ui-button.d.ts +29 -0
- package/types/joanroucoux-cairn-ui-card.d.ts +36 -0
- package/types/joanroucoux-cairn-ui-choice-chips.d.ts +46 -0
- package/types/joanroucoux-cairn-ui-control.d.ts +27 -0
- package/types/joanroucoux-cairn-ui-delta.d.ts +38 -0
- package/types/joanroucoux-cairn-ui-dialog.d.ts +72 -0
- package/types/joanroucoux-cairn-ui-donut.d.ts +59 -0
- package/types/joanroucoux-cairn-ui-empty.d.ts +19 -0
- package/types/joanroucoux-cairn-ui-external-link.d.ts +15 -0
- package/types/joanroucoux-cairn-ui-fact.d.ts +44 -0
- package/types/joanroucoux-cairn-ui-field.d.ts +84 -0
- package/types/joanroucoux-cairn-ui-filter-chips.d.ts +28 -0
- package/types/joanroucoux-cairn-ui-input.d.ts +51 -0
- package/types/joanroucoux-cairn-ui-line-chart.d.ts +114 -0
- package/types/joanroucoux-cairn-ui-menu.d.ts +66 -0
- package/types/joanroucoux-cairn-ui-meter.d.ts +30 -0
- package/types/joanroucoux-cairn-ui-motion.d.ts +124 -0
- package/types/joanroucoux-cairn-ui-nav-item.d.ts +21 -0
- package/types/joanroucoux-cairn-ui-row.d.ts +78 -0
- package/types/joanroucoux-cairn-ui-segmented.d.ts +36 -0
- package/types/joanroucoux-cairn-ui-select.d.ts +23 -0
- package/types/joanroucoux-cairn-ui-skeleton.d.ts +26 -0
- package/types/joanroucoux-cairn-ui-stale-link.d.ts +24 -0
- package/types/joanroucoux-cairn-ui-stat.d.ts +32 -0
- package/types/joanroucoux-cairn-ui-switch.d.ts +17 -0
- package/types/joanroucoux-cairn-ui-tab-bar.d.ts +31 -0
- package/types/joanroucoux-cairn-ui-table.d.ts +139 -0
- package/types/joanroucoux-cairn-ui-toast.d.ts +51 -0
- package/types/joanroucoux-cairn-ui.d.ts +1 -1440
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,77 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.1
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- The npm page now carries a README: what the package is, how to install it, the stylesheets and the `@source`
|
|
8
|
+
line to add, the `fonts.css` setup and the per-folder imports.
|
|
9
|
+
- The Storybook is published at https://joanroucoux.github.io/cairn-ui/ on every push to `main`.
|
|
10
|
+
- A GitHub Release is created for every `vX.Y.Z` tag, with the matching section of this changelog as its notes.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Stories and specs use fictional data. No component or style changes.
|
|
15
|
+
|
|
16
|
+
## 0.8.0
|
|
17
|
+
|
|
18
|
+
### Breaking
|
|
19
|
+
|
|
20
|
+
- The package root `@joanroucoux/cairn-ui` no longer exports anything. Each component folder is now its own
|
|
21
|
+
entry point, imported as `@joanroucoux/cairn-ui/<folder>`. A consumer's bundler splits chunks per module: a
|
|
22
|
+
single bundle, or a root barrel re-exporting the entries, put every component in the initial chunk. Moving to
|
|
23
|
+
subpaths lets each lazy route pull in only the components it uses.
|
|
24
|
+
- Migration: replace `import { UiButton } from '@joanroucoux/cairn-ui'` with
|
|
25
|
+
`import { UiButton } from '@joanroucoux/cairn-ui/button'`. The stylesheets under `@joanroucoux/cairn-ui/styles/*`
|
|
26
|
+
are unchanged.
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- `@joanroucoux/cairn-ui/control` (`UI_CONTROL`, `UiControl`, `UiControlError`) and, from
|
|
31
|
+
`@joanroucoux/cairn-ui/input`, `CONTROL_BASE_CLASSES`, `CONTROL_SIZE_CLASSES` and `CONTROL_SURFACE_CLASSES` are now
|
|
32
|
+
exported, because `select`, `choice-chips` and `field` import them across entry points. Only `UiControlError` was
|
|
33
|
+
public before.
|
|
34
|
+
|
|
35
|
+
### Subpaths
|
|
36
|
+
|
|
37
|
+
Every symbol that was exported from the root now lives under exactly one subpath:
|
|
38
|
+
|
|
39
|
+
- `@joanroucoux/cairn-ui/action-bar`: `UiActionBar`
|
|
40
|
+
- `@joanroucoux/cairn-ui/alert`: `ALERT_VARIANTS`, `UiAlert`, `AlertVariant`
|
|
41
|
+
- `@joanroucoux/cairn-ui/amount`: `AMOUNT_MASKINGS`, `AMOUNT_NUMERICS`, `UI_AMOUNT_MASKED`, `UiAmount`, `AmountMasking`, `AmountNumeric`, `AmountFormatOptions`, `formatAmount`
|
|
42
|
+
- `@joanroucoux/cairn-ui/async`: `ASYNC_ALIGNS`, `ASYNC_STATES`, `ASYNC_VARIANTS`, `UiAsync`, `AsyncAlign`, `AsyncState`, `AsyncVariant`, `delayedState`
|
|
43
|
+
- `@joanroucoux/cairn-ui/avatar`: `AVATAR_SIZES`, `UiAvatar`, `UiAvatarLink`, `AvatarSize`
|
|
44
|
+
- `@joanroucoux/cairn-ui/back-link`: `BACK_LINK_SIZES`, `UiBackLink`, `BackLinkSize`
|
|
45
|
+
- `@joanroucoux/cairn-ui/badge`: `BADGE_SIZES`, `BADGE_VARIANTS`, `UiBadge`, `BadgeSize`, `BadgeVariant`
|
|
46
|
+
- `@joanroucoux/cairn-ui/button`: `BUTTON_SIZES`, `BUTTON_VARIANTS`, `UiButton`, `ButtonSize`, `ButtonVariant`
|
|
47
|
+
- `@joanroucoux/cairn-ui/card`: `CARD_PADDINGS`, `CARD_SURFACES`, `CARD_VARIANTS`, `UiCard`, `CardPadding`, `CardSurface`, `CardVariant`
|
|
48
|
+
- `@joanroucoux/cairn-ui/choice-chips`: `ChoiceChipOption`, `UiChoiceChips`
|
|
49
|
+
- `@joanroucoux/cairn-ui/control`: `UiControlError`, `UI_CONTROL`, `UiControl`
|
|
50
|
+
- `@joanroucoux/cairn-ui/delta`: `DELTA_EMPHASES`, `DELTA_SIZES`, `DELTA_WEIGHTS`, `UiDelta`, `DeltaEmphasis`, `DeltaSize`, `DeltaWeight`
|
|
51
|
+
- `@joanroucoux/cairn-ui/dialog`: `DIALOG_CLOSE_REASONS`, `DIALOG_LAYOUTS`, `DIALOG_WIDTHS`, `UiDialog`, `DialogCloseReason`, `DialogLayout`, `DialogWidth`
|
|
52
|
+
- `@joanroucoux/cairn-ui/donut`: `DonutSlice`, `UiDonut`
|
|
53
|
+
- `@joanroucoux/cairn-ui/empty`: `UiEmpty`
|
|
54
|
+
- `@joanroucoux/cairn-ui/external-link`: `UiExternalLink`
|
|
55
|
+
- `@joanroucoux/cairn-ui/fact`: `FACT_SIZES`, `FACT_SUB_TONES`, `UiFact`, `UiFacts`, `FactSize`, `FactSubTone`
|
|
56
|
+
- `@joanroucoux/cairn-ui/field`: `UiField`, `UiFieldLeading`, `UiFieldTrailing`
|
|
57
|
+
- `@joanroucoux/cairn-ui/filter-chips`: `FilterChipOption`, `UiFilterChips`
|
|
58
|
+
- `@joanroucoux/cairn-ui/input`: `CONTROL_SIZES`, `CONTROL_SURFACES`, `UiInput`, `UiTextarea`, `ControlSize`, `ControlSurface`, `CONTROL_BASE_CLASSES`, `CONTROL_SIZE_CLASSES`, `CONTROL_SURFACE_CLASSES`
|
|
59
|
+
- `@joanroucoux/cairn-ui/line-chart`: `AxisTicks`, `ChartPoint`, `TooltipSize`, `UiLineChart`
|
|
60
|
+
- `@joanroucoux/cairn-ui/menu`: `UiMenu`, `UiMenuItem`, `UiMenuTrigger`
|
|
61
|
+
- `@joanroucoux/cairn-ui/meter`: `METER_SIZES`, `METER_TONES`, `UiMeter`, `MeterSize`, `MeterTone`
|
|
62
|
+
- `@joanroucoux/cairn-ui/motion`: `UiFlipItem`, `UiFlipList`, `UiHighlight`, `injectReducedMotion`, `holdTransitionsUntilRendered`
|
|
63
|
+
- `@joanroucoux/cairn-ui/nav-item`: `UiNavItem`
|
|
64
|
+
- `@joanroucoux/cairn-ui/row`: `ROW_GAPS`, `ROW_PADDINGS`, `ROW_SIZES`, `UiListRow`, `UiRow`, `UiRowItem`, `UiRowTile`, `RowGap`, `RowPadding`, `RowSize`
|
|
65
|
+
- `@joanroucoux/cairn-ui/segmented`: `SEGMENTED_SIZES`, `UiSegmented`, `SegmentedOption`, `SegmentedSize`
|
|
66
|
+
- `@joanroucoux/cairn-ui/select`: `UiSelect`
|
|
67
|
+
- `@joanroucoux/cairn-ui/skeleton`: `SKELETON_SHAPES`, `UiSkeleton`, `SkeletonShape`
|
|
68
|
+
- `@joanroucoux/cairn-ui/stale-link`: `STALE_LINK_SIZES`, `UiStaleLink`, `StaleLinkSize`
|
|
69
|
+
- `@joanroucoux/cairn-ui/stat`: `STAT_SIZES`, `UiStat`, `StatSize`
|
|
70
|
+
- `@joanroucoux/cairn-ui/switch`: `UiSwitch`
|
|
71
|
+
- `@joanroucoux/cairn-ui/tab-bar`: `UiTab`, `UiTabBar`
|
|
72
|
+
- `@joanroucoux/cairn-ui/table`: `CELL_BREAKPOINTS`, `GROUP_SIZES`, `SUB_TONES`, `TABLE_ROWS`, `UiCellSub`, `UiGroup`, `UiGroupCell`, `UiRowAction`, `UiRowLink`, `UiTable`, `UiTd`, `UiTh`, `UiTr`, `CellBreakpoint`, `GroupSize`, `SubTone`, `TableRow`
|
|
73
|
+
- `@joanroucoux/cairn-ui/toast`: `UiToaster`, `UiToasts`, `Toast`
|
|
74
|
+
|
|
3
75
|
## 0.7.2
|
|
4
76
|
|
|
5
77
|
### Added
|
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# @joanroucoux/cairn-ui
|
|
2
|
+
|
|
3
|
+
A monochrome, accessible Angular design system, built for [Cairn](https://github.com/JoanRoucoux/cairn).
|
|
4
|
+
|
|
5
|
+
Browse every component, live, in the [Storybook](https://joanroucoux.github.io/cairn-ui/).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @joanroucoux/cairn-ui
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Peer dependencies: `@angular/common` and `@angular/core`, version 22. The styles use Tailwind CSS 4, which the consuming
|
|
14
|
+
application provides.
|
|
15
|
+
|
|
16
|
+
## Setup
|
|
17
|
+
|
|
18
|
+
Import three stylesheets, in order, and register the package as a Tailwind source (templates in `node_modules` are
|
|
19
|
+
not scanned by default):
|
|
20
|
+
|
|
21
|
+
```css
|
|
22
|
+
@import 'tailwindcss';
|
|
23
|
+
@import '@joanroucoux/cairn-ui/styles/tokens.css';
|
|
24
|
+
@import '@joanroucoux/cairn-ui/styles/theme.css';
|
|
25
|
+
@import '@joanroucoux/cairn-ui/styles/motion.css';
|
|
26
|
+
@source '../node_modules/@joanroucoux/cairn-ui';
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`fonts.css` self-hosts Rubik and must not be imported through the Tailwind entry above: its `@font-face` rules use
|
|
30
|
+
`url(./fonts/...)` paths relative to the file, and Tailwind's CSS-first pipeline does not rebase them. Add it to the
|
|
31
|
+
`styles` array of `angular.json` instead:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
"styles": ["src/styles.css", "node_modules/@joanroucoux/cairn-ui/styles/fonts.css"]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
An application that does not use Tailwind can import `@joanroucoux/cairn-ui/styles/tokens.css` alone: it is a pure
|
|
38
|
+
token sheet.
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
The package root exports nothing. Each component folder is its own entry point, so your bundler only ships what you
|
|
43
|
+
import:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { Component } from '@angular/core';
|
|
47
|
+
|
|
48
|
+
import { UiBadge } from '@joanroucoux/cairn-ui/badge';
|
|
49
|
+
import { UiButton } from '@joanroucoux/cairn-ui/button';
|
|
50
|
+
|
|
51
|
+
@Component({
|
|
52
|
+
selector: 'app-example',
|
|
53
|
+
imports: [UiButton, UiBadge],
|
|
54
|
+
template: `
|
|
55
|
+
<ui-badge>Synced</ui-badge>
|
|
56
|
+
<button ui-button>Save</button>
|
|
57
|
+
`,
|
|
58
|
+
})
|
|
59
|
+
export class Example {}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## More
|
|
63
|
+
|
|
64
|
+
- [Repository README](https://github.com/JoanRoucoux/cairn-ui#readme): the component list, design principles and development guide
|
|
65
|
+
- [Changelog](https://github.com/JoanRoucoux/cairn-ui/blob/main/projects/ui/CHANGELOG.md)
|
|
66
|
+
|
|
67
|
+
MIT licensed.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { DOCUMENT } from '@angular/common';
|
|
2
|
+
import * as i0 from '@angular/core';
|
|
3
|
+
import { inject, ElementRef, DestroyRef, afterNextRender, Component } from '@angular/core';
|
|
4
|
+
|
|
5
|
+
const HEIGHT_PROPERTY = '--action-bar-height';
|
|
6
|
+
/**
|
|
7
|
+
* Bar of actions fixed above the tab bar on iPhone: the primary actions of a detail screen, within
|
|
8
|
+
* thumb reach. It sits `--tab-bar-height` (52px plus the safe area) above the bottom edge, so it clears `ui-tab-bar`, and
|
|
9
|
+
* it is hidden from `64rem`, where the same actions live in the page itself. Its children share the
|
|
10
|
+
* width equally. While it is on the page it publishes its height as `--action-bar-height` on the
|
|
11
|
+
* root element, which `ui-toaster` adds to its offset so a confirmation never covers the actions.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* <ui-action-bar>
|
|
15
|
+
* <button ui-button variant="outline" size="tall">Sell</button>
|
|
16
|
+
* <button ui-button size="tall">Buy</button>
|
|
17
|
+
* </ui-action-bar>
|
|
18
|
+
*/
|
|
19
|
+
class UiActionBar {
|
|
20
|
+
constructor() {
|
|
21
|
+
const host = inject(ElementRef).nativeElement;
|
|
22
|
+
const root = inject(DOCUMENT).documentElement;
|
|
23
|
+
const destroyRef = inject(DestroyRef);
|
|
24
|
+
const publish = () => root.style.setProperty(HEIGHT_PROPERTY, `${host.offsetHeight}px`);
|
|
25
|
+
afterNextRender(() => {
|
|
26
|
+
publish();
|
|
27
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
28
|
+
const observer = new ResizeObserver(publish);
|
|
29
|
+
observer.observe(host);
|
|
30
|
+
destroyRef.onDestroy(() => observer.disconnect());
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
destroyRef.onDestroy(() => root.style.removeProperty(HEIGHT_PROPERTY));
|
|
34
|
+
}
|
|
35
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiActionBar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
36
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: UiActionBar, isStandalone: true, selector: "ui-action-bar", host: { classAttribute: "fixed inset-x-0 bottom-[var(--tab-bar-height,calc(52px+env(safe-area-inset-bottom)))] z-40 grid grid-flow-col auto-cols-fr gap-2 bg-(--background) px-(--gutter) py-3 shadow-[0_-1px_0_var(--hairline)] lg:hidden" }, ngImport: i0, template: '<ng-content />', isInline: true });
|
|
37
|
+
}
|
|
38
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiActionBar, decorators: [{
|
|
39
|
+
type: Component,
|
|
40
|
+
args: [{
|
|
41
|
+
selector: 'ui-action-bar',
|
|
42
|
+
template: '<ng-content />',
|
|
43
|
+
host: {
|
|
44
|
+
class: 'fixed inset-x-0 bottom-[var(--tab-bar-height,calc(52px+env(safe-area-inset-bottom)))] z-40 grid grid-flow-col auto-cols-fr gap-2 bg-(--background) px-(--gutter) py-3 shadow-[0_-1px_0_var(--hairline)] lg:hidden',
|
|
45
|
+
},
|
|
46
|
+
}]
|
|
47
|
+
}], ctorParameters: () => [] });
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Generated bundle index. Do not edit.
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
export { UiActionBar };
|
|
54
|
+
//# sourceMappingURL=joanroucoux-cairn-ui-action-bar.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"joanroucoux-cairn-ui-action-bar.mjs","sources":["../../../projects/ui/action-bar/action-bar.ts","../../../projects/ui/action-bar/joanroucoux-cairn-ui-action-bar.ts"],"sourcesContent":["import { DOCUMENT } from '@angular/common';\nimport { Component, DestroyRef, ElementRef, afterNextRender, inject } from '@angular/core';\n\nconst HEIGHT_PROPERTY = '--action-bar-height';\n\n/**\n * Bar of actions fixed above the tab bar on iPhone: the primary actions of a detail screen, within\n * thumb reach. It sits `--tab-bar-height` (52px plus the safe area) above the bottom edge, so it clears `ui-tab-bar`, and\n * it is hidden from `64rem`, where the same actions live in the page itself. Its children share the\n * width equally. While it is on the page it publishes its height as `--action-bar-height` on the\n * root element, which `ui-toaster` adds to its offset so a confirmation never covers the actions.\n *\n * @example\n * <ui-action-bar>\n * <button ui-button variant=\"outline\" size=\"tall\">Sell</button>\n * <button ui-button size=\"tall\">Buy</button>\n * </ui-action-bar>\n */\n@Component({\n selector: 'ui-action-bar',\n template: '<ng-content />',\n host: {\n class:\n 'fixed inset-x-0 bottom-[var(--tab-bar-height,calc(52px+env(safe-area-inset-bottom)))] z-40 grid grid-flow-col auto-cols-fr gap-2 bg-(--background) px-(--gutter) py-3 shadow-[0_-1px_0_var(--hairline)] lg:hidden',\n },\n})\nexport class UiActionBar {\n constructor() {\n const host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n const root = inject(DOCUMENT).documentElement;\n const destroyRef = inject(DestroyRef);\n const publish = (): void => root.style.setProperty(HEIGHT_PROPERTY, `${host.offsetHeight}px`);\n\n afterNextRender(() => {\n publish();\n\n if (typeof ResizeObserver !== 'undefined') {\n const observer = new ResizeObserver(publish);\n observer.observe(host);\n destroyRef.onDestroy(() => observer.disconnect());\n }\n });\n\n destroyRef.onDestroy(() => root.style.removeProperty(HEIGHT_PROPERTY));\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAGA,MAAM,eAAe,GAAG,qBAAqB;AAE7C;;;;;;;;;;;;AAYG;MASU,WAAW,CAAA;AACtB,IAAA,WAAA,GAAA;QACE,MAAM,IAAI,GAAG,MAAM,CAA0B,UAAU,CAAC,CAAC,aAAa;QACtE,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,eAAe;AAC7C,QAAA,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;AACrC,QAAA,MAAM,OAAO,GAAG,MAAY,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,eAAe,EAAE,CAAA,EAAG,IAAI,CAAC,YAAY,CAAA,EAAA,CAAI,CAAC;QAE7F,eAAe,CAAC,MAAK;AACnB,YAAA,OAAO,EAAE;AAET,YAAA,IAAI,OAAO,cAAc,KAAK,WAAW,EAAE;AACzC,gBAAA,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,OAAO,CAAC;AAC5C,gBAAA,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC;gBACtB,UAAU,CAAC,SAAS,CAAC,MAAM,QAAQ,CAAC,UAAU,EAAE,CAAC;YACnD;AACF,QAAA,CAAC,CAAC;AAEF,QAAA,UAAU,CAAC,SAAS,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,eAAe,CAAC,CAAC;IACxE;uGAlBW,WAAW,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAX,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,WAAW,wTANZ,gBAAgB,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA;;2FAMf,WAAW,EAAA,UAAA,EAAA,CAAA;kBARvB,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,eAAe;AACzB,oBAAA,QAAQ,EAAE,gBAAgB;AAC1B,oBAAA,IAAI,EAAE;AACJ,wBAAA,KAAK,EACH,mNAAmN;AACtN,qBAAA;AACF,iBAAA;;;ACzBD;;AAEG;;;;"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, booleanAttribute, computed, Component } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
const ALERT_VARIANTS = ['error', 'warning'];
|
|
5
|
+
const BASE_CLASSES = 'flex items-start gap-2.5 text-left text-label';
|
|
6
|
+
const VARIANT_CLASSES = {
|
|
7
|
+
error: 'rounded-control bg-(--card) px-3.5 py-3 shadow-[inset_0_0_0_1px_var(--border)]',
|
|
8
|
+
warning: 'text-(--foreground)',
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Inline message explaining a failure or a consequence: an icon, an optional heading and a text.
|
|
12
|
+
* It fades in when it appears; set `[fadeIn]="false"` on an alert that is part of the page as it
|
|
13
|
+
* opens (an error state after a load), which shows without motion.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* <ui-alert heading="Sign-in failed">Try again.</ui-alert>
|
|
17
|
+
*/
|
|
18
|
+
class UiAlert {
|
|
19
|
+
variant = input('error', /* @ts-ignore */
|
|
20
|
+
...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
21
|
+
heading = input(/* @ts-ignore */
|
|
22
|
+
...(ngDevMode ? [undefined, { debugName: "heading" }] : /* istanbul ignore next */ []));
|
|
23
|
+
fadeIn = input(true, { ...(ngDevMode ? { debugName: "fadeIn" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
24
|
+
classes = computed(() => `${BASE_CLASSES} ${VARIANT_CLASSES[this.variant()]}${this.fadeIn() ? ' ui-enter-fade' : ''}`, /* @ts-ignore */
|
|
25
|
+
...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
26
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiAlert, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
27
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiAlert, isStandalone: true, selector: "ui-alert", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, fadeIn: { classPropertyName: "fadeIn", publicName: "fadeIn", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "animate.leave": "ui-leave-fade" }, properties: { "class": "classes()", "attr.role": "variant() === 'warning' ? 'status' : 'alert'" } }, ngImport: i0, template: `
|
|
28
|
+
@if (variant() === 'warning') {
|
|
29
|
+
<svg
|
|
30
|
+
aria-hidden="true"
|
|
31
|
+
class="mt-px block flex-none stroke-(--negative)"
|
|
32
|
+
fill="none"
|
|
33
|
+
height="18"
|
|
34
|
+
stroke-linecap="round"
|
|
35
|
+
stroke-linejoin="round"
|
|
36
|
+
stroke-width="1.75"
|
|
37
|
+
viewBox="0 0 24 24"
|
|
38
|
+
width="18"
|
|
39
|
+
>
|
|
40
|
+
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" />
|
|
41
|
+
<path d="M12 9v4" />
|
|
42
|
+
<path d="M12 17h.01" />
|
|
43
|
+
</svg>
|
|
44
|
+
} @else {
|
|
45
|
+
<svg
|
|
46
|
+
aria-hidden="true"
|
|
47
|
+
class="block flex-none stroke-(--negative)"
|
|
48
|
+
fill="none"
|
|
49
|
+
height="18"
|
|
50
|
+
stroke-linecap="round"
|
|
51
|
+
stroke-linejoin="round"
|
|
52
|
+
stroke-width="1.75"
|
|
53
|
+
viewBox="0 0 24 24"
|
|
54
|
+
width="18"
|
|
55
|
+
[class.mt-px]="heading()"
|
|
56
|
+
>
|
|
57
|
+
<circle cx="12" cy="12" r="10" />
|
|
58
|
+
<line x1="12" x2="12" y1="8" y2="12" />
|
|
59
|
+
<line x1="12" x2="12.01" y1="16" y2="16" />
|
|
60
|
+
</svg>
|
|
61
|
+
}
|
|
62
|
+
<div class="flex flex-col gap-0.5">
|
|
63
|
+
@if (heading()) {
|
|
64
|
+
<span class="font-medium">{{ heading() }}</span>
|
|
65
|
+
}
|
|
66
|
+
<span class="text-pretty" [class.text-(--muted-foreground)]="heading()"><ng-content /></span>
|
|
67
|
+
</div>
|
|
68
|
+
`, isInline: true });
|
|
69
|
+
}
|
|
70
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiAlert, decorators: [{
|
|
71
|
+
type: Component,
|
|
72
|
+
args: [{
|
|
73
|
+
selector: 'ui-alert',
|
|
74
|
+
template: `
|
|
75
|
+
@if (variant() === 'warning') {
|
|
76
|
+
<svg
|
|
77
|
+
aria-hidden="true"
|
|
78
|
+
class="mt-px block flex-none stroke-(--negative)"
|
|
79
|
+
fill="none"
|
|
80
|
+
height="18"
|
|
81
|
+
stroke-linecap="round"
|
|
82
|
+
stroke-linejoin="round"
|
|
83
|
+
stroke-width="1.75"
|
|
84
|
+
viewBox="0 0 24 24"
|
|
85
|
+
width="18"
|
|
86
|
+
>
|
|
87
|
+
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" />
|
|
88
|
+
<path d="M12 9v4" />
|
|
89
|
+
<path d="M12 17h.01" />
|
|
90
|
+
</svg>
|
|
91
|
+
} @else {
|
|
92
|
+
<svg
|
|
93
|
+
aria-hidden="true"
|
|
94
|
+
class="block flex-none stroke-(--negative)"
|
|
95
|
+
fill="none"
|
|
96
|
+
height="18"
|
|
97
|
+
stroke-linecap="round"
|
|
98
|
+
stroke-linejoin="round"
|
|
99
|
+
stroke-width="1.75"
|
|
100
|
+
viewBox="0 0 24 24"
|
|
101
|
+
width="18"
|
|
102
|
+
[class.mt-px]="heading()"
|
|
103
|
+
>
|
|
104
|
+
<circle cx="12" cy="12" r="10" />
|
|
105
|
+
<line x1="12" x2="12" y1="8" y2="12" />
|
|
106
|
+
<line x1="12" x2="12.01" y1="16" y2="16" />
|
|
107
|
+
</svg>
|
|
108
|
+
}
|
|
109
|
+
<div class="flex flex-col gap-0.5">
|
|
110
|
+
@if (heading()) {
|
|
111
|
+
<span class="font-medium">{{ heading() }}</span>
|
|
112
|
+
}
|
|
113
|
+
<span class="text-pretty" [class.text-(--muted-foreground)]="heading()"><ng-content /></span>
|
|
114
|
+
</div>
|
|
115
|
+
`,
|
|
116
|
+
host: {
|
|
117
|
+
'animate.leave': 'ui-leave-fade',
|
|
118
|
+
'[class]': 'classes()',
|
|
119
|
+
'[attr.role]': "variant() === 'warning' ? 'status' : 'alert'",
|
|
120
|
+
},
|
|
121
|
+
}]
|
|
122
|
+
}], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], fadeIn: [{ type: i0.Input, args: [{ isSignal: true, alias: "fadeIn", required: false }] }] } });
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Generated bundle index. Do not edit.
|
|
126
|
+
*/
|
|
127
|
+
|
|
128
|
+
export { ALERT_VARIANTS, UiAlert };
|
|
129
|
+
//# sourceMappingURL=joanroucoux-cairn-ui-alert.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"joanroucoux-cairn-ui-alert.mjs","sources":["../../../projects/ui/alert/alert.ts","../../../projects/ui/alert/joanroucoux-cairn-ui-alert.ts"],"sourcesContent":["import { Component, booleanAttribute, computed, input } from '@angular/core';\n\nexport const ALERT_VARIANTS = ['error', 'warning'] as const;\nexport type AlertVariant = (typeof ALERT_VARIANTS)[number];\n\nconst BASE_CLASSES = 'flex items-start gap-2.5 text-left text-label';\n\nconst VARIANT_CLASSES: Record<AlertVariant, string> = {\n error: 'rounded-control bg-(--card) px-3.5 py-3 shadow-[inset_0_0_0_1px_var(--border)]',\n warning: 'text-(--foreground)',\n};\n\n/**\n * Inline message explaining a failure or a consequence: an icon, an optional heading and a text.\n * It fades in when it appears; set `[fadeIn]=\"false\"` on an alert that is part of the page as it\n * opens (an error state after a load), which shows without motion.\n *\n * @example\n * <ui-alert heading=\"Sign-in failed\">Try again.</ui-alert>\n */\n@Component({\n selector: 'ui-alert',\n template: `\n @if (variant() === 'warning') {\n <svg\n aria-hidden=\"true\"\n class=\"mt-px block flex-none stroke-(--negative)\"\n fill=\"none\"\n height=\"18\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n viewBox=\"0 0 24 24\"\n width=\"18\"\n >\n <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" />\n <path d=\"M12 9v4\" />\n <path d=\"M12 17h.01\" />\n </svg>\n } @else {\n <svg\n aria-hidden=\"true\"\n class=\"block flex-none stroke-(--negative)\"\n fill=\"none\"\n height=\"18\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n viewBox=\"0 0 24 24\"\n width=\"18\"\n [class.mt-px]=\"heading()\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <line x1=\"12\" x2=\"12\" y1=\"8\" y2=\"12\" />\n <line x1=\"12\" x2=\"12.01\" y1=\"16\" y2=\"16\" />\n </svg>\n }\n <div class=\"flex flex-col gap-0.5\">\n @if (heading()) {\n <span class=\"font-medium\">{{ heading() }}</span>\n }\n <span class=\"text-pretty\" [class.text-(--muted-foreground)]=\"heading()\"><ng-content /></span>\n </div>\n `,\n host: {\n 'animate.leave': 'ui-leave-fade',\n '[class]': 'classes()',\n '[attr.role]': \"variant() === 'warning' ? 'status' : 'alert'\",\n },\n})\nexport class UiAlert {\n readonly variant = input<AlertVariant>('error');\n readonly heading = input<string>();\n readonly fadeIn = input(true, { transform: booleanAttribute });\n\n protected readonly classes = computed(\n () => `${BASE_CLASSES} ${VARIANT_CLASSES[this.variant()]}${this.fadeIn() ? ' ui-enter-fade' : ''}`,\n );\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;MAEa,cAAc,GAAG,CAAC,OAAO,EAAE,SAAS;AAGjD,MAAM,YAAY,GAAG,+CAA+C;AAEpE,MAAM,eAAe,GAAiC;AACpD,IAAA,KAAK,EAAE,gFAAgF;AACvF,IAAA,OAAO,EAAE,qBAAqB;CAC/B;AAED;;;;;;;AAOG;MAmDU,OAAO,CAAA;IACT,OAAO,GAAG,KAAK,CAAe,OAAO;gFAAC;AACtC,IAAA,OAAO,GAAG,KAAK;2FAAU;IACzB,MAAM,GAAG,KAAK,CAAC,IAAI,8EAAI,SAAS,EAAE,gBAAgB,EAAA,CAAG;AAE3C,IAAA,OAAO,GAAG,QAAQ,CACnC,MAAM,CAAA,EAAG,YAAY,CAAA,CAAA,EAAI,eAAe,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAA,EAAG,IAAI,CAAC,MAAM,EAAE,GAAG,gBAAgB,GAAG,EAAE,CAAA,CAAE;gFACnG;uGAPU,OAAO,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAP,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,OAAO,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,UAAA,EAAA,MAAA,EAAA,EAAA,OAAA,EAAA,EAAA,iBAAA,EAAA,SAAA,EAAA,UAAA,EAAA,SAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,OAAA,EAAA,EAAA,iBAAA,EAAA,SAAA,EAAA,UAAA,EAAA,SAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,MAAA,EAAA,EAAA,iBAAA,EAAA,QAAA,EAAA,UAAA,EAAA,QAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,eAAA,EAAA,eAAA,EAAA,EAAA,UAAA,EAAA,EAAA,OAAA,EAAA,WAAA,EAAA,WAAA,EAAA,8CAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAhDR;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCT,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA;;2FAOU,OAAO,EAAA,UAAA,EAAA,CAAA;kBAlDnB,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,UAAU;AACpB,oBAAA,QAAQ,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCT,EAAA,CAAA;AACD,oBAAA,IAAI,EAAE;AACJ,wBAAA,eAAe,EAAE,eAAe;AAChC,wBAAA,SAAS,EAAE,WAAW;AACtB,wBAAA,aAAa,EAAE,8CAA8C;AAC9D,qBAAA;AACF,iBAAA;;;ACrED;;AAEG;;;;"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, signal, input, inject, LOCALE_ID, booleanAttribute, computed, Component } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
const MINUS = '−';
|
|
5
|
+
const EM_DASH = '—';
|
|
6
|
+
const BULLETS = '••••';
|
|
7
|
+
/** Formats a number or an amount for the locale, with a missing value as an em dash and masking behind four bullets. */
|
|
8
|
+
function formatAmount(value, options, masked = false) {
|
|
9
|
+
if (value === null || value === undefined || Number.isNaN(value)) {
|
|
10
|
+
return EM_DASH;
|
|
11
|
+
}
|
|
12
|
+
const digits = options.fractionDigits ?? 2;
|
|
13
|
+
const format = new Intl.NumberFormat(options.locale, {
|
|
14
|
+
...(options.currency ? { style: 'currency', currency: options.currency } : {}),
|
|
15
|
+
minimumFractionDigits: digits,
|
|
16
|
+
maximumFractionDigits: digits,
|
|
17
|
+
});
|
|
18
|
+
const magnitude = Math.abs(value);
|
|
19
|
+
const parts = format.formatToParts(magnitude);
|
|
20
|
+
if (masked) {
|
|
21
|
+
let replaced = false;
|
|
22
|
+
return parts
|
|
23
|
+
.map((part) => {
|
|
24
|
+
if (['integer', 'group', 'decimal', 'fraction'].includes(part.type)) {
|
|
25
|
+
if (replaced) {
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
replaced = true;
|
|
29
|
+
return BULLETS;
|
|
30
|
+
}
|
|
31
|
+
return part.value;
|
|
32
|
+
})
|
|
33
|
+
.join('');
|
|
34
|
+
}
|
|
35
|
+
const text = parts.map((part) => part.value).join('');
|
|
36
|
+
const isZero = Number(format.format(magnitude).replace(/[^\d]/g, '')) === 0;
|
|
37
|
+
if (!options.signed || isZero) {
|
|
38
|
+
return value < 0 && !isZero ? `${MINUS}${text}` : text;
|
|
39
|
+
}
|
|
40
|
+
return `${value > 0 ? '+' : MINUS}${text}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** App-wide "amounts are hidden" state. Defaults to never masked; the app overrides it. */
|
|
44
|
+
const UI_AMOUNT_MASKED = new InjectionToken('UI_AMOUNT_MASKED', {
|
|
45
|
+
providedIn: 'root',
|
|
46
|
+
factory: () => signal(false),
|
|
47
|
+
});
|
|
48
|
+
/** Available figure styles. `AmountNumeric` is derived from this tuple. */
|
|
49
|
+
const AMOUNT_NUMERICS = ['tabular', 'proportional'];
|
|
50
|
+
/** Available masking behaviors. `AmountMasking` is derived from this tuple. */
|
|
51
|
+
const AMOUNT_MASKINGS = ['dots', 'hide'];
|
|
52
|
+
/**
|
|
53
|
+
* A number or an amount of money, formatted for the locale and masked when the app hides amounts.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* <ui-amount [value]="total" currency="EUR" numeric="proportional" />
|
|
57
|
+
* <ui-amount [value]="dayChange" currency="EUR" signed whenMasked="hide" />
|
|
58
|
+
*/
|
|
59
|
+
class UiAmount {
|
|
60
|
+
value = input(/* @ts-ignore */
|
|
61
|
+
...(ngDevMode ? [undefined, { debugName: "value" }] : /* istanbul ignore next */ []));
|
|
62
|
+
currency = input(/* @ts-ignore */
|
|
63
|
+
...(ngDevMode ? [undefined, { debugName: "currency" }] : /* istanbul ignore next */ []));
|
|
64
|
+
locale = input(inject(LOCALE_ID), /* @ts-ignore */
|
|
65
|
+
...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
|
|
66
|
+
signed = input(false, { ...(ngDevMode ? { debugName: "signed" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
67
|
+
fractionDigits = input(2, /* @ts-ignore */
|
|
68
|
+
...(ngDevMode ? [{ debugName: "fractionDigits" }] : /* istanbul ignore next */ []));
|
|
69
|
+
numeric = input('tabular', /* @ts-ignore */
|
|
70
|
+
...(ngDevMode ? [{ debugName: "numeric" }] : /* istanbul ignore next */ []));
|
|
71
|
+
whenMasked = input('dots', /* @ts-ignore */
|
|
72
|
+
...(ngDevMode ? [{ debugName: "whenMasked" }] : /* istanbul ignore next */ []));
|
|
73
|
+
#masked = inject(UI_AMOUNT_MASKED);
|
|
74
|
+
hidden = computed(() => this.#masked() && this.whenMasked() === 'hide', /* @ts-ignore */
|
|
75
|
+
...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
|
|
76
|
+
text = computed(() => formatAmount(this.value(), {
|
|
77
|
+
locale: this.locale(),
|
|
78
|
+
currency: this.currency(),
|
|
79
|
+
signed: this.signed(),
|
|
80
|
+
fractionDigits: this.fractionDigits(),
|
|
81
|
+
}, this.#masked()), /* @ts-ignore */
|
|
82
|
+
...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
|
|
83
|
+
classes = computed(() => {
|
|
84
|
+
const missing = this.value() === null || this.value() === undefined;
|
|
85
|
+
return [
|
|
86
|
+
'whitespace-nowrap',
|
|
87
|
+
this.numeric() === 'tabular' ? 'tabular-nums' : '',
|
|
88
|
+
missing ? 'text-(--subtle-foreground)' : '',
|
|
89
|
+
]
|
|
90
|
+
.filter(Boolean)
|
|
91
|
+
.join(' ');
|
|
92
|
+
}, /* @ts-ignore */
|
|
93
|
+
...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
94
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiAmount, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
95
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.7", type: UiAmount, isStandalone: true, selector: "ui-amount", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, currency: { classPropertyName: "currency", publicName: "currency", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, signed: { classPropertyName: "signed", publicName: "signed", isSignal: true, isRequired: false, transformFunction: null }, fractionDigits: { classPropertyName: "fractionDigits", publicName: "fractionDigits", isSignal: true, isRequired: false, transformFunction: null }, numeric: { classPropertyName: "numeric", publicName: "numeric", isSignal: true, isRequired: false, transformFunction: null }, whenMasked: { classPropertyName: "whenMasked", publicName: "whenMasked", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()", "attr.hidden": "hidden() || null" } }, ngImport: i0, template: '{{ text() }}', isInline: true });
|
|
96
|
+
}
|
|
97
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiAmount, decorators: [{
|
|
98
|
+
type: Component,
|
|
99
|
+
args: [{
|
|
100
|
+
selector: 'ui-amount',
|
|
101
|
+
template: '{{ text() }}',
|
|
102
|
+
host: {
|
|
103
|
+
'[class]': 'classes()',
|
|
104
|
+
'[attr.hidden]': 'hidden() || null',
|
|
105
|
+
},
|
|
106
|
+
}]
|
|
107
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], currency: [{ type: i0.Input, args: [{ isSignal: true, alias: "currency", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], signed: [{ type: i0.Input, args: [{ isSignal: true, alias: "signed", required: false }] }], fractionDigits: [{ type: i0.Input, args: [{ isSignal: true, alias: "fractionDigits", required: false }] }], numeric: [{ type: i0.Input, args: [{ isSignal: true, alias: "numeric", required: false }] }], whenMasked: [{ type: i0.Input, args: [{ isSignal: true, alias: "whenMasked", required: false }] }] } });
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Generated bundle index. Do not edit.
|
|
111
|
+
*/
|
|
112
|
+
|
|
113
|
+
export { AMOUNT_MASKINGS, AMOUNT_NUMERICS, UI_AMOUNT_MASKED, UiAmount, formatAmount };
|
|
114
|
+
//# sourceMappingURL=joanroucoux-cairn-ui-amount.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"joanroucoux-cairn-ui-amount.mjs","sources":["../../../projects/ui/amount/format-amount.ts","../../../projects/ui/amount/amount.ts","../../../projects/ui/amount/joanroucoux-cairn-ui-amount.ts"],"sourcesContent":["const MINUS = '−';\nconst EM_DASH = '—';\nconst BULLETS = '••••';\n\nexport type AmountFormatOptions = {\n locale: string;\n currency?: string;\n signed?: boolean;\n fractionDigits?: number;\n};\n\n/** Formats a number or an amount for the locale, with a missing value as an em dash and masking behind four bullets. */\nexport function formatAmount(value: number | null | undefined, options: AmountFormatOptions, masked = false): string {\n if (value === null || value === undefined || Number.isNaN(value)) {\n return EM_DASH;\n }\n const digits = options.fractionDigits ?? 2;\n const format = new Intl.NumberFormat(options.locale, {\n ...(options.currency ? { style: 'currency', currency: options.currency } : {}),\n minimumFractionDigits: digits,\n maximumFractionDigits: digits,\n });\n const magnitude = Math.abs(value);\n const parts = format.formatToParts(magnitude);\n\n if (masked) {\n let replaced = false;\n return parts\n .map((part) => {\n if (['integer', 'group', 'decimal', 'fraction'].includes(part.type)) {\n if (replaced) {\n return '';\n }\n replaced = true;\n return BULLETS;\n }\n return part.value;\n })\n .join('');\n }\n\n const text = parts.map((part) => part.value).join('');\n const isZero = Number(format.format(magnitude).replace(/[^\\d]/g, '')) === 0;\n if (!options.signed || isZero) {\n return value < 0 && !isZero ? `${MINUS}${text}` : text;\n }\n return `${value > 0 ? '+' : MINUS}${text}`;\n}\n","import {\n Component,\n InjectionToken,\n LOCALE_ID,\n type Signal,\n booleanAttribute,\n computed,\n inject,\n input,\n signal,\n} from '@angular/core';\n\nimport { formatAmount } from './format-amount';\n\n/** App-wide \"amounts are hidden\" state. Defaults to never masked; the app overrides it. */\nexport const UI_AMOUNT_MASKED = new InjectionToken<Signal<boolean>>('UI_AMOUNT_MASKED', {\n providedIn: 'root',\n factory: () => signal(false),\n});\n\n/** Available figure styles. `AmountNumeric` is derived from this tuple. */\nexport const AMOUNT_NUMERICS = ['tabular', 'proportional'] as const;\nexport type AmountNumeric = (typeof AMOUNT_NUMERICS)[number];\n\n/** Available masking behaviors. `AmountMasking` is derived from this tuple. */\nexport const AMOUNT_MASKINGS = ['dots', 'hide'] as const;\nexport type AmountMasking = (typeof AMOUNT_MASKINGS)[number];\n\n/**\n * A number or an amount of money, formatted for the locale and masked when the app hides amounts.\n *\n * @example\n * <ui-amount [value]=\"total\" currency=\"EUR\" numeric=\"proportional\" />\n * <ui-amount [value]=\"dayChange\" currency=\"EUR\" signed whenMasked=\"hide\" />\n */\n@Component({\n selector: 'ui-amount',\n template: '{{ text() }}',\n host: {\n '[class]': 'classes()',\n '[attr.hidden]': 'hidden() || null',\n },\n})\nexport class UiAmount {\n readonly value = input<number | null | undefined>();\n readonly currency = input<string>();\n readonly locale = input<string>(inject(LOCALE_ID));\n readonly signed = input(false, { transform: booleanAttribute });\n readonly fractionDigits = input(2);\n readonly numeric = input<AmountNumeric>('tabular');\n readonly whenMasked = input<AmountMasking>('dots');\n\n readonly #masked = inject(UI_AMOUNT_MASKED);\n\n protected readonly hidden = computed(() => this.#masked() && this.whenMasked() === 'hide');\n\n protected readonly text = computed(() =>\n formatAmount(\n this.value(),\n {\n locale: this.locale(),\n currency: this.currency(),\n signed: this.signed(),\n fractionDigits: this.fractionDigits(),\n },\n this.#masked(),\n ),\n );\n\n protected readonly classes = computed(() => {\n const missing = this.value() === null || this.value() === undefined;\n\n return [\n 'whitespace-nowrap',\n this.numeric() === 'tabular' ? 'tabular-nums' : '',\n missing ? 'text-(--subtle-foreground)' : '',\n ]\n .filter(Boolean)\n .join(' ');\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|