@surfnet/curve-angular 0.2.0

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.
Files changed (189) hide show
  1. package/.postcssrc.json +7 -0
  2. package/.storybook/main.ts +9 -0
  3. package/.storybook/manager.ts +3 -0
  4. package/.storybook/preview.ts +28 -0
  5. package/.storybook/tsconfig.json +8 -0
  6. package/.turbo/turbo-build.log +32 -0
  7. package/CHANGELOG.md +29 -0
  8. package/angular.json +52 -0
  9. package/components.json +5 -0
  10. package/dist/fesm2022/surfnet-curve-angular.mjs +4237 -0
  11. package/dist/fesm2022/surfnet-curve-angular.mjs.map +1 -0
  12. package/dist/files/geist-cyrillic-ext-wght-italic.woff2 +0 -0
  13. package/dist/files/geist-cyrillic-ext-wght-normal.woff2 +0 -0
  14. package/dist/files/geist-cyrillic-wght-italic.woff2 +0 -0
  15. package/dist/files/geist-cyrillic-wght-normal.woff2 +0 -0
  16. package/dist/files/geist-latin-ext-wght-italic.woff2 +0 -0
  17. package/dist/files/geist-latin-ext-wght-normal.woff2 +0 -0
  18. package/dist/files/geist-latin-wght-italic.woff2 +0 -0
  19. package/dist/files/geist-latin-wght-normal.woff2 +0 -0
  20. package/dist/files/geist-vietnamese-wght-italic.woff2 +0 -0
  21. package/dist/files/geist-vietnamese-wght-normal.woff2 +0 -0
  22. package/dist/styles.css +2 -0
  23. package/dist/types/surfnet-curve-angular.d.ts +1017 -0
  24. package/ng-package.json +20 -0
  25. package/package.json +73 -0
  26. package/scripts/copy-font-files.ts +15 -0
  27. package/scripts/rewrite-helm-imports.ts +60 -0
  28. package/src/foundations/design-tokens.stories.ts +198 -0
  29. package/src/lib/ui/avatar/src/index.ts +22 -0
  30. package/src/lib/ui/avatar/src/lib/hlm-avatar-badge.ts +19 -0
  31. package/src/lib/ui/avatar/src/lib/hlm-avatar-fallback.ts +20 -0
  32. package/src/lib/ui/avatar/src/lib/hlm-avatar-group-count.ts +17 -0
  33. package/src/lib/ui/avatar/src/lib/hlm-avatar-group.ts +17 -0
  34. package/src/lib/ui/avatar/src/lib/hlm-avatar-image.ts +19 -0
  35. package/src/lib/ui/avatar/src/lib/hlm-avatar.stories.ts +107 -0
  36. package/src/lib/ui/avatar/src/lib/hlm-avatar.ts +32 -0
  37. package/src/lib/ui/breadcrumb/src/index.ts +30 -0
  38. package/src/lib/ui/breadcrumb/src/lib/hlm-breadcrumb-ellipsis.ts +36 -0
  39. package/src/lib/ui/breadcrumb/src/lib/hlm-breadcrumb-item.ts +14 -0
  40. package/src/lib/ui/breadcrumb/src/lib/hlm-breadcrumb-link.ts +36 -0
  41. package/src/lib/ui/breadcrumb/src/lib/hlm-breadcrumb-list.ts +17 -0
  42. package/src/lib/ui/breadcrumb/src/lib/hlm-breadcrumb-page.ts +17 -0
  43. package/src/lib/ui/breadcrumb/src/lib/hlm-breadcrumb-separator.ts +27 -0
  44. package/src/lib/ui/breadcrumb/src/lib/hlm-breadcrumb.stories.ts +81 -0
  45. package/src/lib/ui/breadcrumb/src/lib/hlm-breadcrumb.ts +13 -0
  46. package/src/lib/ui/button/src/index.ts +6 -0
  47. package/src/lib/ui/button/src/lib/hlm-button.stories.ts +170 -0
  48. package/src/lib/ui/button/src/lib/hlm-button.token.ts +22 -0
  49. package/src/lib/ui/button/src/lib/hlm-button.ts +76 -0
  50. package/src/lib/ui/card/src/index.ts +25 -0
  51. package/src/lib/ui/card/src/lib/hlm-card-action.ts +12 -0
  52. package/src/lib/ui/card/src/lib/hlm-card-content.ts +12 -0
  53. package/src/lib/ui/card/src/lib/hlm-card-description.ts +12 -0
  54. package/src/lib/ui/card/src/lib/hlm-card-footer.ts +14 -0
  55. package/src/lib/ui/card/src/lib/hlm-card-header.ts +15 -0
  56. package/src/lib/ui/card/src/lib/hlm-card-title.ts +12 -0
  57. package/src/lib/ui/card/src/lib/hlm-card.stories.ts +105 -0
  58. package/src/lib/ui/card/src/lib/hlm-card.token.ts +19 -0
  59. package/src/lib/ui/card/src/lib/hlm-card.ts +22 -0
  60. package/src/lib/ui/checkbox/src/index.ts +5 -0
  61. package/src/lib/ui/checkbox/src/lib/hlm-checkbox.stories.ts +78 -0
  62. package/src/lib/ui/checkbox/src/lib/hlm-checkbox.ts +160 -0
  63. package/src/lib/ui/data-table/src/index.ts +14 -0
  64. package/src/lib/ui/data-table/src/lib/hlm-data-table-content.ts +87 -0
  65. package/src/lib/ui/data-table/src/lib/hlm-data-table-pagination.ts +62 -0
  66. package/src/lib/ui/data-table/src/lib/hlm-data-table-toolbar.ts +16 -0
  67. package/src/lib/ui/data-table/src/lib/hlm-data-table.stories.ts +479 -0
  68. package/src/lib/ui/data-table/src/lib/inject-data-table.ts +86 -0
  69. package/src/lib/ui/dropdown-menu/src/index.ts +45 -0
  70. package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu-checkbox-indicator.ts +20 -0
  71. package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu-checkbox.ts +36 -0
  72. package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu-group.ts +16 -0
  73. package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu-item-sub-indicator.ts +17 -0
  74. package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu-item.ts +41 -0
  75. package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu-label.ts +20 -0
  76. package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu-radio-indicator.ts +20 -0
  77. package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu-radio.ts +36 -0
  78. package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu-separator.ts +14 -0
  79. package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu-shortcut.ts +14 -0
  80. package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu-sub.ts +58 -0
  81. package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu-token.ts +26 -0
  82. package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu-trigger.ts +49 -0
  83. package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu.stories.ts +125 -0
  84. package/src/lib/ui/dropdown-menu/src/lib/hlm-dropdown-menu.ts +62 -0
  85. package/src/lib/ui/field/src/index.ts +34 -0
  86. package/src/lib/ui/field/src/lib/hlm-field-content.ts +12 -0
  87. package/src/lib/ui/field/src/lib/hlm-field-description.ts +53 -0
  88. package/src/lib/ui/field/src/lib/hlm-field-error.ts +100 -0
  89. package/src/lib/ui/field/src/lib/hlm-field-group.ts +15 -0
  90. package/src/lib/ui/field/src/lib/hlm-field-label.ts +17 -0
  91. package/src/lib/ui/field/src/lib/hlm-field-legend.ts +17 -0
  92. package/src/lib/ui/field/src/lib/hlm-field-separator.ts +24 -0
  93. package/src/lib/ui/field/src/lib/hlm-field-set.ts +15 -0
  94. package/src/lib/ui/field/src/lib/hlm-field-title.ts +15 -0
  95. package/src/lib/ui/field/src/lib/hlm-field.stories.ts +117 -0
  96. package/src/lib/ui/field/src/lib/hlm-field.ts +49 -0
  97. package/src/lib/ui/icon/src/index.ts +7 -0
  98. package/src/lib/ui/icon/src/lib/hlm-icon.token.ts +20 -0
  99. package/src/lib/ui/icon/src/lib/hlm-icon.ts +42 -0
  100. package/src/lib/ui/input/src/index.ts +5 -0
  101. package/src/lib/ui/input/src/lib/hlm-input.stories.ts +94 -0
  102. package/src/lib/ui/input/src/lib/hlm-input.ts +20 -0
  103. package/src/lib/ui/input-group/src/index.ts +22 -0
  104. package/src/lib/ui/input-group/src/lib/hlm-input-group-addon.ts +40 -0
  105. package/src/lib/ui/input-group/src/lib/hlm-input-group-button.ts +47 -0
  106. package/src/lib/ui/input-group/src/lib/hlm-input-group-input.ts +17 -0
  107. package/src/lib/ui/input-group/src/lib/hlm-input-group-text.ts +14 -0
  108. package/src/lib/ui/input-group/src/lib/hlm-input-group-textarea.ts +17 -0
  109. package/src/lib/ui/input-group/src/lib/hlm-input-group.stories.ts +133 -0
  110. package/src/lib/ui/input-group/src/lib/hlm-input-group.ts +18 -0
  111. package/src/lib/ui/label/src/index.ts +5 -0
  112. package/src/lib/ui/label/src/lib/hlm-label.stories.ts +55 -0
  113. package/src/lib/ui/label/src/lib/hlm-label.ts +17 -0
  114. package/src/lib/ui/select/src/index.ts +52 -0
  115. package/src/lib/ui/select/src/lib/hlm-select-content.ts +44 -0
  116. package/src/lib/ui/select/src/lib/hlm-select-group.ts +14 -0
  117. package/src/lib/ui/select/src/lib/hlm-select-item.ts +36 -0
  118. package/src/lib/ui/select/src/lib/hlm-select-label.ts +14 -0
  119. package/src/lib/ui/select/src/lib/hlm-select-multiple.ts +45 -0
  120. package/src/lib/ui/select/src/lib/hlm-select-placeholder.ts +17 -0
  121. package/src/lib/ui/select/src/lib/hlm-select-portal.ts +8 -0
  122. package/src/lib/ui/select/src/lib/hlm-select-scroll-down.ts +22 -0
  123. package/src/lib/ui/select/src/lib/hlm-select-scroll-up.ts +22 -0
  124. package/src/lib/ui/select/src/lib/hlm-select-separator.ts +14 -0
  125. package/src/lib/ui/select/src/lib/hlm-select-trigger.ts +59 -0
  126. package/src/lib/ui/select/src/lib/hlm-select-value-template.ts +5 -0
  127. package/src/lib/ui/select/src/lib/hlm-select-value.ts +18 -0
  128. package/src/lib/ui/select/src/lib/hlm-select-values-content.ts +9 -0
  129. package/src/lib/ui/select/src/lib/hlm-select-values.ts +5 -0
  130. package/src/lib/ui/select/src/lib/hlm-select.stories.ts +105 -0
  131. package/src/lib/ui/select/src/lib/hlm-select.ts +45 -0
  132. package/src/lib/ui/separator/src/index.ts +5 -0
  133. package/src/lib/ui/separator/src/lib/hlm-separator.stories.ts +88 -0
  134. package/src/lib/ui/separator/src/lib/hlm-separator.ts +19 -0
  135. package/src/lib/ui/sheet/src/index.ts +34 -0
  136. package/src/lib/ui/sheet/src/lib/hlm-sheet-close.ts +9 -0
  137. package/src/lib/ui/sheet/src/lib/hlm-sheet-content.ts +66 -0
  138. package/src/lib/ui/sheet/src/lib/hlm-sheet-description.ts +14 -0
  139. package/src/lib/ui/sheet/src/lib/hlm-sheet-footer.ts +12 -0
  140. package/src/lib/ui/sheet/src/lib/hlm-sheet-header.ts +12 -0
  141. package/src/lib/ui/sheet/src/lib/hlm-sheet-overlay.ts +27 -0
  142. package/src/lib/ui/sheet/src/lib/hlm-sheet-portal.ts +8 -0
  143. package/src/lib/ui/sheet/src/lib/hlm-sheet-title.ts +14 -0
  144. package/src/lib/ui/sheet/src/lib/hlm-sheet-trigger.ts +9 -0
  145. package/src/lib/ui/sheet/src/lib/hlm-sheet.ts +29 -0
  146. package/src/lib/ui/sidebar/src/index.ts +75 -0
  147. package/src/lib/ui/sidebar/src/lib/hlm-sidebar-content.ts +18 -0
  148. package/src/lib/ui/sidebar/src/lib/hlm-sidebar-footer.ts +15 -0
  149. package/src/lib/ui/sidebar/src/lib/hlm-sidebar-group-action.ts +18 -0
  150. package/src/lib/ui/sidebar/src/lib/hlm-sidebar-group-content.ts +15 -0
  151. package/src/lib/ui/sidebar/src/lib/hlm-sidebar-group-label.ts +18 -0
  152. package/src/lib/ui/sidebar/src/lib/hlm-sidebar-group.ts +15 -0
  153. package/src/lib/ui/sidebar/src/lib/hlm-sidebar-header.ts +15 -0
  154. package/src/lib/ui/sidebar/src/lib/hlm-sidebar-input.ts +17 -0
  155. package/src/lib/ui/sidebar/src/lib/hlm-sidebar-inset.ts +15 -0
  156. package/src/lib/ui/sidebar/src/lib/hlm-sidebar-menu-action.ts +24 -0
  157. package/src/lib/ui/sidebar/src/lib/hlm-sidebar-menu-badge.ts +18 -0
  158. package/src/lib/ui/sidebar/src/lib/hlm-sidebar-menu-button.ts +97 -0
  159. package/src/lib/ui/sidebar/src/lib/hlm-sidebar-menu-item.ts +15 -0
  160. package/src/lib/ui/sidebar/src/lib/hlm-sidebar-menu-skeleton.ts +33 -0
  161. package/src/lib/ui/sidebar/src/lib/hlm-sidebar-menu-sub-button.ts +41 -0
  162. package/src/lib/ui/sidebar/src/lib/hlm-sidebar-menu-sub-item.ts +15 -0
  163. package/src/lib/ui/sidebar/src/lib/hlm-sidebar-menu-sub.ts +18 -0
  164. package/src/lib/ui/sidebar/src/lib/hlm-sidebar-menu.ts +15 -0
  165. package/src/lib/ui/sidebar/src/lib/hlm-sidebar-rail.ts +34 -0
  166. package/src/lib/ui/sidebar/src/lib/hlm-sidebar-separator.ts +17 -0
  167. package/src/lib/ui/sidebar/src/lib/hlm-sidebar-trigger.ts +35 -0
  168. package/src/lib/ui/sidebar/src/lib/hlm-sidebar-wrapper.ts +24 -0
  169. package/src/lib/ui/sidebar/src/lib/hlm-sidebar.service.ts +136 -0
  170. package/src/lib/ui/sidebar/src/lib/hlm-sidebar.stories.ts +177 -0
  171. package/src/lib/ui/sidebar/src/lib/hlm-sidebar.token.ts +33 -0
  172. package/src/lib/ui/sidebar/src/lib/hlm-sidebar.ts +139 -0
  173. package/src/lib/ui/skeleton/src/index.ts +5 -0
  174. package/src/lib/ui/skeleton/src/lib/hlm-skeleton.ts +14 -0
  175. package/src/lib/ui/table/src/index.ts +25 -0
  176. package/src/lib/ui/table/src/lib/hlm-table.stories.ts +70 -0
  177. package/src/lib/ui/table/src/lib/hlm-table.ts +129 -0
  178. package/src/lib/ui/textarea/src/index.ts +5 -0
  179. package/src/lib/ui/textarea/src/lib/hlm-textarea.stories.ts +72 -0
  180. package/src/lib/ui/textarea/src/lib/hlm-textarea.ts +21 -0
  181. package/src/lib/ui/tooltip/src/index.ts +5 -0
  182. package/src/lib/ui/tooltip/src/lib/hlm-tooltip.ts +44 -0
  183. package/src/lib/ui/utils/src/index.ts +1 -0
  184. package/src/lib/ui/utils/src/lib/hlm.ts +313 -0
  185. package/src/public-api.ts +24 -0
  186. package/src/styles.css +43 -0
  187. package/tsconfig.json +45 -0
  188. package/tsconfig.lib.json +11 -0
  189. package/tsconfig.lib.prod.json +9 -0
@@ -0,0 +1,47 @@
1
+ import { Directive, input } from '@angular/core';
2
+ import { HlmButton, provideBrnButtonConfig } from '../../../button/src';
3
+ import { classes } from '../../../utils/src';
4
+ import { cva, type VariantProps } from 'class-variance-authority';
5
+
6
+ const inputGroupAddonVariants = cva('gap-2 text-sm flex items-center shadow-none', {
7
+ variants: {
8
+ size: {
9
+ xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-1.5 [&>ng-icon:not([class*='text-'])]:text-[calc(var(--spacing)*3.5)]",
10
+ sm: '',
11
+ 'icon-xs': 'size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>ng-icon]:p-0',
12
+ 'icon-sm': 'size-8 p-0 has-[>ng-icon]:p-0',
13
+ },
14
+ },
15
+ defaultVariants: {
16
+ size: 'xs',
17
+ },
18
+ });
19
+
20
+ type InputGroupAddonVariants = VariantProps<typeof inputGroupAddonVariants>;
21
+
22
+ @Directive({
23
+ selector: 'button[hlmInputGroupButton]',
24
+ providers: [
25
+ provideBrnButtonConfig({
26
+ variant: 'ghost',
27
+ }),
28
+ ],
29
+ hostDirectives: [
30
+ {
31
+ directive: HlmButton,
32
+ inputs: ['variant'],
33
+ },
34
+ ],
35
+ host: {
36
+ '[attr.data-size]': 'size()',
37
+ '[type]': 'type()',
38
+ },
39
+ })
40
+ export class HlmInputGroupButton {
41
+ public readonly size = input<InputGroupAddonVariants['size']>('xs');
42
+ public readonly type = input<'button' | 'submit' | 'reset'>('button');
43
+
44
+ constructor() {
45
+ classes(() => inputGroupAddonVariants({ size: this.size() }));
46
+ }
47
+ }
@@ -0,0 +1,17 @@
1
+ import { Directive } from '@angular/core';
2
+ import { HlmInput } from '../../../input/src';
3
+ import { classes } from '../../../utils/src';
4
+
5
+ @Directive({
6
+ selector: 'input[hlmInputGroupInput]',
7
+ hostDirectives: [HlmInput],
8
+ host: { 'data-slot': 'input-group-control' },
9
+ })
10
+ export class HlmInputGroupInput {
11
+ constructor() {
12
+ classes(
13
+ () =>
14
+ `rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 data-[matches-spartan-invalid=true]:ring-0 dark:bg-transparent flex-1`,
15
+ );
16
+ }
17
+ }
@@ -0,0 +1,14 @@
1
+ import { Directive } from '@angular/core';
2
+ import { classes } from '../../../utils/src';
3
+
4
+ @Directive({
5
+ selector: '[hlmInputGroupText],hlm-input-group-text',
6
+ })
7
+ export class HlmInputGroupText {
8
+ constructor() {
9
+ classes(
10
+ () =>
11
+ "text-muted-foreground gap-2 text-sm [&_ng-icon:not([class*='text-'])]:text-[calc(var(--spacing)*4)] flex items-center [&_ng-icon]:pointer-events-none",
12
+ );
13
+ }
14
+ }
@@ -0,0 +1,17 @@
1
+ import { Directive } from '@angular/core';
2
+ import { HlmTextarea } from '../../../textarea/src';
3
+ import { classes } from '../../../utils/src';
4
+
5
+ @Directive({
6
+ selector: 'textarea[hlmInputGroupTextarea]',
7
+ hostDirectives: [HlmTextarea],
8
+ host: { 'data-slot': 'input-group-control' },
9
+ })
10
+ export class HlmInputGroupTextarea {
11
+ constructor() {
12
+ classes(
13
+ () =>
14
+ 'rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 data-[matches-spartan-invalid=true]:ring-0 dark:bg-transparent flex-1 resize-none',
15
+ );
16
+ }
17
+ }
@@ -0,0 +1,133 @@
1
+ import { ReactiveFormsModule } from '@angular/forms';
2
+ import { NgIcon, provideIcons } from '@ng-icons/core';
3
+ import {
4
+ phosphorInfo,
5
+ phosphorMagnifyingGlass,
6
+ phosphorPaperPlaneTilt,
7
+ phosphorPlus,
8
+ } from '@ng-icons/phosphor-icons/regular';
9
+ import type { Meta, StoryObj } from '@storybook/angular';
10
+ import { moduleMetadata } from '@storybook/angular';
11
+ import { inputGroupContract } from '@surfnet/curve-contracts';
12
+ import { HlmInputGroup, HlmInputGroupImports } from '..';
13
+ import { HlmDropdownMenuImports } from '../../../dropdown-menu/src';
14
+
15
+ const meta: Meta<HlmInputGroup> = {
16
+ title: 'Components/Input Group',
17
+ component: HlmInputGroup,
18
+ decorators: [
19
+ moduleMetadata({
20
+ imports: [HlmInputGroupImports, HlmDropdownMenuImports, NgIcon, ReactiveFormsModule],
21
+ providers: [
22
+ provideIcons({
23
+ phosphorInfo,
24
+ phosphorMagnifyingGlass,
25
+ phosphorPaperPlaneTilt,
26
+ phosphorPlus,
27
+ }),
28
+ ],
29
+ }),
30
+ ],
31
+ parameters: {
32
+ docs: {
33
+ description: {
34
+ component: inputGroupContract.docs.description,
35
+ },
36
+ },
37
+ },
38
+ };
39
+
40
+ export default meta;
41
+ type Story = StoryObj<HlmInputGroup>;
42
+
43
+ /** Search field with a leading icon and trailing result count. */
44
+ export const WithLeadingAndTrailingAddon: Story = {
45
+ render: () => ({
46
+ template: `
47
+ <div class="w-full max-w-sm">
48
+ <div hlmInputGroup>
49
+ <div hlmInputGroupAddon>
50
+ <ng-icon name="phosphorMagnifyingGlass" />
51
+ </div>
52
+ <input hlmInputGroupInput placeholder="Search…" />
53
+ <div hlmInputGroupAddon align="inline-end">12 results</div>
54
+ </div>
55
+ </div>
56
+ `,
57
+ }),
58
+ };
59
+
60
+ /** URL input with a text prefix on the leading side. */
61
+ export const WithTextPrefix: Story = {
62
+ render: () => ({
63
+ template: `
64
+ <div class="w-full max-w-sm">
65
+ <div hlmInputGroup>
66
+ <div hlmInputGroupAddon>
67
+ <span hlmInputGroupText>https://</span>
68
+ </div>
69
+ <input hlmInputGroupInput placeholder="example.com" class="!pl-1" />
70
+ <div hlmInputGroupAddon align="inline-end">
71
+ <button hlmInputGroupButton class="rounded-full" size="icon-xs" [hlmTooltip]="'This is content in a tooltip.'">
72
+ <ng-icon name="phosphorInfo" />
73
+ </button>
74
+ </div>
75
+ </div>
76
+ </div>
77
+ `,
78
+ }),
79
+ };
80
+
81
+ /** Category select prefix — picking an option updates the leading trigger label. */
82
+ export const WithLeadingDropdown: Story = {
83
+ render: () => ({
84
+ props: {
85
+ category: 'All',
86
+ categories: ['All', 'Articles', 'Pages'],
87
+ select(this: { category: string }, value: string) {
88
+ this.category = value;
89
+ },
90
+ },
91
+ template: `
92
+ <div class="w-full max-w-sm">
93
+ <div hlmInputGroup>
94
+ <div hlmInputGroupAddon>
95
+ <button hlmInputGroupButton variant="ghost" [hlmDropdownMenuTrigger]="menu">{{ category }}</button>
96
+ <ng-template #menu>
97
+ <hlm-dropdown-menu>
98
+ @for (option of categories; track option) {
99
+ <button hlmDropdownMenuItem (click)="select(option)">{{ option }}</button>
100
+ }
101
+ </hlm-dropdown-menu>
102
+ </ng-template>
103
+ </div>
104
+ <input hlmInputGroupInput [placeholder]="'Search ' + category.toLowerCase() + '…'" />
105
+ </div>
106
+ </div>
107
+ `,
108
+ }),
109
+ };
110
+
111
+ /** Chat-style textarea with a block-end toolbar row. */
112
+ export const TextareaWithBlockEndToolbar: Story = {
113
+ render: () => ({
114
+ template: `
115
+ <div class="w-full max-w-sm">
116
+ <div hlmInputGroup>
117
+ <textarea hlmInputGroupTextarea placeholder="Ask, Search or Chat…"></textarea>
118
+ <div hlmInputGroupAddon align="block-end">
119
+ <button hlmInputGroupButton variant="outline" class="rounded-full" size="icon-xs">
120
+ <ng-icon name="phosphorPlus" />
121
+ </button>
122
+ <span hlmInputGroupText class="ml-auto">52% used</span>
123
+ <hlm-separator orientation="vertical" class="!h-4" />
124
+ <button hlmInputGroupButton variant="default" class="rounded-full" size="icon-xs" disabled>
125
+ <ng-icon name="phosphorPaperPlaneTilt" />
126
+ <span class="sr-only">Send</span>
127
+ </button>
128
+ </div>
129
+ </div>
130
+ </div>
131
+ `,
132
+ }),
133
+ };
@@ -0,0 +1,18 @@
1
+ import { Directive } from '@angular/core';
2
+ import { classes } from '../../../utils/src';
3
+
4
+ @Directive({
5
+ selector: '[hlmInputGroup],hlm-input-group',
6
+ host: {
7
+ 'data-slot': 'input-group',
8
+ role: 'group',
9
+ },
10
+ })
11
+ export class HlmInputGroup {
12
+ constructor() {
13
+ classes(
14
+ () =>
15
+ 'border-input dark:bg-input/30 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][data-matches-spartan-invalid=true]]:ring-destructive/20 has-[[data-slot][data-matches-spartan-invalid=true]]:border-destructive dark:has-[[data-slot][data-matches-spartan-invalid=true]]:ring-destructive/40 h-9 rounded-md border shadow-xs transition-[color,box-shadow] in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot][data-matches-spartan-invalid=true]]:ring-3 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pe-1.5 has-[>[data-align=inline-start]]:[&>input]:ps-1.5 group/input-group relative flex w-full min-w-0 items-center outline-none has-[>textarea]:h-auto',
16
+ );
17
+ }
18
+ }
@@ -0,0 +1,5 @@
1
+ import { HlmLabel } from './lib/hlm-label';
2
+
3
+ export * from './lib/hlm-label';
4
+
5
+ export const HlmLabelImports = [HlmLabel] as const;
@@ -0,0 +1,55 @@
1
+ import { argsToTemplate, moduleMetadata, type Meta, type StoryObj } from '@storybook/angular';
2
+ import { labelContract } from '@surfnet/curve-contracts';
3
+ import { HlmLabel } from '..';
4
+ import { HlmField, HlmInput } from '../../../../../public-api';
5
+
6
+ const meta: Meta<HlmLabel> = {
7
+ title: 'Components/Label',
8
+ component: HlmLabel,
9
+ decorators: [
10
+ moduleMetadata({
11
+ imports: [HlmField, HlmInput],
12
+ }),
13
+ ],
14
+ parameters: {
15
+ docs: {
16
+ description: {
17
+ component: labelContract.docs.description,
18
+ },
19
+ },
20
+ },
21
+ args: {
22
+ children: 'Email',
23
+ },
24
+ };
25
+
26
+ export default meta;
27
+ type Story = StoryObj<HlmLabel>;
28
+
29
+ /** A standalone label. */
30
+ export const Default: Story = {
31
+ render: (args) => ({
32
+ props: args,
33
+ template: `
34
+ <div ${argsToTemplate(args)}>
35
+ <label hlmLabel>
36
+ {{children}}
37
+ </label>
38
+ </div>
39
+ `,
40
+ }),
41
+ };
42
+
43
+ /** A label wired to an input through `htmlFor`. */
44
+ export const WithInput: Story = {
45
+ render: () => ({
46
+ template: `
47
+ <div class="w-full max-w-md">
48
+ <div hlmField class="w-72">
49
+ <label hlmLabel for="email">Email</label>
50
+ <input hlmInput id="email" placeholder="you@surf.nl"/>
51
+ </div>
52
+ </div>
53
+ `,
54
+ }),
55
+ };
@@ -0,0 +1,17 @@
1
+ import { Directive } from '@angular/core';
2
+ import { BrnLabel } from '@spartan-ng/brain/label';
3
+ import { classes } from '../../../utils/src';
4
+
5
+ @Directive({
6
+ selector: '[hlmLabel]',
7
+ hostDirectives: [{ directive: BrnLabel, inputs: ['id', 'for'] }],
8
+ host: { 'data-slot': 'label' },
9
+ })
10
+ export class HlmLabel {
11
+ constructor() {
12
+ classes(
13
+ () =>
14
+ 'gap-2 text-sm leading-none font-medium group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed',
15
+ );
16
+ }
17
+ }
@@ -0,0 +1,52 @@
1
+ import { HlmSelect } from './lib/hlm-select';
2
+ import { HlmSelectContent } from './lib/hlm-select-content';
3
+ import { HlmSelectGroup } from './lib/hlm-select-group';
4
+ import { HlmSelectItem } from './lib/hlm-select-item';
5
+ import { HlmSelectLabel } from './lib/hlm-select-label';
6
+ import { HlmSelectMultiple } from './lib/hlm-select-multiple';
7
+ import { HlmSelectPlaceholder } from './lib/hlm-select-placeholder';
8
+ import { HlmSelectPortal } from './lib/hlm-select-portal';
9
+ import { HlmSelectScrollDown } from './lib/hlm-select-scroll-down';
10
+ import { HlmSelectScrollUp } from './lib/hlm-select-scroll-up';
11
+ import { HlmSelectSeparator } from './lib/hlm-select-separator';
12
+ import { HlmSelectTrigger } from './lib/hlm-select-trigger';
13
+ import { HlmSelectValue } from './lib/hlm-select-value';
14
+ import { HlmSelectValueTemplate } from './lib/hlm-select-value-template';
15
+ import { HlmSelectValues } from './lib/hlm-select-values';
16
+ import { HlmSelectValuesContent } from './lib/hlm-select-values-content';
17
+
18
+ export * from './lib/hlm-select';
19
+ export * from './lib/hlm-select-content';
20
+ export * from './lib/hlm-select-group';
21
+ export * from './lib/hlm-select-item';
22
+ export * from './lib/hlm-select-label';
23
+ export * from './lib/hlm-select-multiple';
24
+ export * from './lib/hlm-select-placeholder';
25
+ export * from './lib/hlm-select-portal';
26
+ export * from './lib/hlm-select-scroll-down';
27
+ export * from './lib/hlm-select-scroll-up';
28
+ export * from './lib/hlm-select-separator';
29
+ export * from './lib/hlm-select-trigger';
30
+ export * from './lib/hlm-select-value';
31
+ export * from './lib/hlm-select-value-template';
32
+ export * from './lib/hlm-select-values';
33
+ export * from './lib/hlm-select-values-content';
34
+
35
+ export const HlmSelectImports = [
36
+ HlmSelect,
37
+ HlmSelectContent,
38
+ HlmSelectGroup,
39
+ HlmSelectItem,
40
+ HlmSelectLabel,
41
+ HlmSelectMultiple,
42
+ HlmSelectPlaceholder,
43
+ HlmSelectPortal,
44
+ HlmSelectScrollDown,
45
+ HlmSelectScrollUp,
46
+ HlmSelectSeparator,
47
+ HlmSelectTrigger,
48
+ HlmSelectValue,
49
+ HlmSelectValues,
50
+ HlmSelectValuesContent,
51
+ HlmSelectValueTemplate,
52
+ ] as const;
@@ -0,0 +1,44 @@
1
+ import { BooleanInput } from '@angular/cdk/coercion';
2
+ import {
3
+ booleanAttribute,
4
+ ChangeDetectionStrategy,
5
+ Component,
6
+ computed,
7
+ input,
8
+ } from '@angular/core';
9
+ import { BrnSelectContent } from '@spartan-ng/brain/select';
10
+ import { classes, hlm } from '../../../utils/src';
11
+ import { HlmSelectScrollDown } from './hlm-select-scroll-down';
12
+ import { HlmSelectScrollUp } from './hlm-select-scroll-up';
13
+
14
+ @Component({
15
+ selector: 'hlm-select-content',
16
+ imports: [HlmSelectScrollUp, HlmSelectScrollDown],
17
+ changeDetection: ChangeDetectionStrategy.OnPush,
18
+ hostDirectives: [BrnSelectContent],
19
+ template: `
20
+ @if (showScroll()) {
21
+ <hlm-select-scroll-up />
22
+ }
23
+
24
+ <div role="listbox" [class]="_computedListboxClasses()">
25
+ <ng-content />
26
+ </div>
27
+
28
+ @if (showScroll()) {
29
+ <hlm-select-scroll-down />
30
+ }
31
+ `,
32
+ })
33
+ export class HlmSelectContent {
34
+ protected readonly _computedListboxClasses = computed(() => hlm('flex flex-col'));
35
+
36
+ public readonly showScroll = input<boolean, BooleanInput>(false, { transform: booleanAttribute });
37
+
38
+ constructor() {
39
+ classes(
40
+ () =>
41
+ 'bg-popover no-scrollbar text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 max-h-72 min-w-36 flex-col rounded-md shadow-md ring-1 duration-100 relative flex w-(--brn-select-width) overflow-x-hidden overflow-y-auto',
42
+ );
43
+ }
44
+ }
@@ -0,0 +1,14 @@
1
+ import { Directive } from '@angular/core';
2
+ import { BrnSelectGroup } from '@spartan-ng/brain/select';
3
+ import { classes } from '../../../utils/src';
4
+
5
+ @Directive({
6
+ selector: '[hlmSelectGroup],hlm-select-group',
7
+ hostDirectives: [{ directive: BrnSelectGroup }],
8
+ host: { 'data-slot': 'select-group' },
9
+ })
10
+ export class HlmSelectGroup {
11
+ constructor() {
12
+ classes(() => 'scroll-my-1 p-1');
13
+ }
14
+ }
@@ -0,0 +1,36 @@
1
+ import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
2
+ import { NgIcon, provideIcons } from '@ng-icons/core';
3
+ import { phosphorCheck } from '@ng-icons/phosphor-icons/regular';
4
+ import { BrnSelectItem } from '@spartan-ng/brain/select';
5
+ import { classes } from '../../../utils/src';
6
+
7
+ @Component({
8
+ selector: 'hlm-select-item',
9
+ imports: [NgIcon],
10
+ providers: [provideIcons({ phosphorCheck })],
11
+ changeDetection: ChangeDetectionStrategy.OnPush,
12
+ hostDirectives: [{ directive: BrnSelectItem, inputs: ['id', 'disabled', 'value'] }],
13
+ host: { 'data-slot': 'select-item' },
14
+ template: `
15
+ <ng-content />
16
+ @if (_active()) {
17
+ <ng-icon
18
+ name="phosphorCheck"
19
+ class="absolute end-2 flex items-center justify-center text-[calc(var(--spacing)*4)]"
20
+ aria-hidden="true"
21
+ />
22
+ }
23
+ `,
24
+ })
25
+ export class HlmSelectItem {
26
+ private readonly _brnSelectItem = inject(BrnSelectItem);
27
+
28
+ protected readonly _active = this._brnSelectItem.active;
29
+
30
+ constructor() {
31
+ classes(
32
+ () =>
33
+ 'data-highlighted:bg-accent data-highlighted:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground gap-2 rounded-sm py-1.5 ps-2 pe-8 text-sm *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 relative flex w-full cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_ng-icon]:pointer-events-none [&_ng-icon]:shrink-0',
34
+ );
35
+ }
36
+ }
@@ -0,0 +1,14 @@
1
+ import { Directive } from '@angular/core';
2
+ import { BrnSelectLabel } from '@spartan-ng/brain/select';
3
+ import { classes } from '../../../utils/src';
4
+
5
+ @Directive({
6
+ selector: '[hlmSelectLabel],hlm-select-label',
7
+ hostDirectives: [{ directive: BrnSelectLabel, inputs: ['id'] }],
8
+ host: { 'data-slot': 'select-label' },
9
+ })
10
+ export class HlmSelectLabel {
11
+ constructor() {
12
+ classes(() => 'text-muted-foreground px-2 py-1.5 text-xs flex');
13
+ }
14
+ }
@@ -0,0 +1,45 @@
1
+ import { Directive } from '@angular/core';
2
+ import { provideBrnDialogDefaultOptions } from '@spartan-ng/brain/dialog';
3
+ import { BrnPopover, provideBrnPopoverConfig } from '@spartan-ng/brain/popover';
4
+ import { BrnSelectMultiple } from '@spartan-ng/brain/select';
5
+ import { classes } from '../../../utils/src';
6
+
7
+ @Directive({
8
+ selector: '[hlmSelectMultiple],hlm-select-multiple',
9
+ providers: [
10
+ provideBrnPopoverConfig({
11
+ align: 'start',
12
+ sideOffset: 6,
13
+ }),
14
+ provideBrnDialogDefaultOptions({
15
+ autoFocus: 'first-heading',
16
+ }),
17
+ ],
18
+ hostDirectives: [
19
+ {
20
+ directive: BrnSelectMultiple,
21
+ inputs: ['disabled', 'value', 'isItemEqualToValue', 'itemToString'],
22
+ outputs: ['valueChange'],
23
+ },
24
+ {
25
+ directive: BrnPopover,
26
+ inputs: [
27
+ 'align',
28
+ 'autoFocus',
29
+ 'closeDelay',
30
+ 'closeOnOutsidePointerEvents',
31
+ 'sideOffset',
32
+ 'state',
33
+ 'offsetX',
34
+ 'restoreFocus',
35
+ ],
36
+ outputs: ['stateChanged', 'closed'],
37
+ },
38
+ ],
39
+ host: { 'data-slot': 'select' },
40
+ })
41
+ export class HlmSelectMultiple {
42
+ constructor() {
43
+ classes(() => 'block');
44
+ }
45
+ }
@@ -0,0 +1,17 @@
1
+ import { Directive } from '@angular/core';
2
+ import { BrnSelectPlaceholder } from '@spartan-ng/brain/select';
3
+ import { classes } from '../../../utils/src';
4
+
5
+ @Directive({
6
+ selector: '[hlmSelectPlaceholder],hlm-select-placeholder',
7
+ hostDirectives: [BrnSelectPlaceholder],
8
+ host: { 'data-slot': 'select-placeholder' },
9
+ })
10
+ export class HlmSelectPlaceholder {
11
+ constructor() {
12
+ classes(
13
+ () =>
14
+ "gap-2 [&_ng-icon:not([class*='text-'])]:text-[calc(var(--spacing)*4)] flex items-center data-hidden:hidden [&_ng-icon]:pointer-events-none [&_ng-icon]:shrink-0",
15
+ );
16
+ }
17
+ }
@@ -0,0 +1,8 @@
1
+ import { Directive } from '@angular/core';
2
+ import { BrnPopoverContent } from '@spartan-ng/brain/popover';
3
+
4
+ @Directive({
5
+ selector: '[hlmSelectPortal]',
6
+ hostDirectives: [{ directive: BrnPopoverContent, inputs: ['context', 'class'] }],
7
+ })
8
+ export class HlmSelectPortal {}
@@ -0,0 +1,22 @@
1
+ import { ChangeDetectionStrategy, Component } from '@angular/core';
2
+ import { NgIcon, provideIcons } from '@ng-icons/core';
3
+ import { phosphorCaretDown } from '@ng-icons/phosphor-icons/regular';
4
+ import { BrnSelectScrollDown } from '@spartan-ng/brain/select';
5
+ import { classes } from '../../../utils/src';
6
+
7
+ @Component({
8
+ selector: 'hlm-select-scroll-down',
9
+ imports: [NgIcon],
10
+ providers: [provideIcons({ phosphorCaretDown })],
11
+ changeDetection: ChangeDetectionStrategy.OnPush,
12
+ hostDirectives: [BrnSelectScrollDown],
13
+ template: ` <ng-icon name="phosphorCaretDown" /> `,
14
+ })
15
+ export class HlmSelectScrollDown {
16
+ constructor() {
17
+ classes(
18
+ () =>
19
+ "bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_ng-icon:not([class*='text-'])]:text-[calc(var(--spacing)*4)] sticky bottom-0 w-full data-hidden:hidden",
20
+ );
21
+ }
22
+ }
@@ -0,0 +1,22 @@
1
+ import { ChangeDetectionStrategy, Component } from '@angular/core';
2
+ import { NgIcon, provideIcons } from '@ng-icons/core';
3
+ import { phosphorCaretUp } from '@ng-icons/phosphor-icons/regular';
4
+ import { BrnSelectScrollUp } from '@spartan-ng/brain/select';
5
+ import { classes } from '../../../utils/src';
6
+
7
+ @Component({
8
+ selector: 'hlm-select-scroll-up',
9
+ imports: [NgIcon],
10
+ providers: [provideIcons({ phosphorCaretUp })],
11
+ changeDetection: ChangeDetectionStrategy.OnPush,
12
+ hostDirectives: [BrnSelectScrollUp],
13
+ template: ` <ng-icon name="phosphorCaretUp" /> `,
14
+ })
15
+ export class HlmSelectScrollUp {
16
+ constructor() {
17
+ classes(
18
+ () =>
19
+ "bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_ng-icon:not([class*='text-'])]:text-[calc(var(--spacing)*4)] sticky top-0 w-full data-hidden:hidden",
20
+ );
21
+ }
22
+ }
@@ -0,0 +1,14 @@
1
+ import { Directive } from '@angular/core';
2
+ import { BrnSelectSeparator } from '@spartan-ng/brain/select';
3
+ import { classes } from '../../../utils/src';
4
+
5
+ @Directive({
6
+ selector: '[hlmSelectSeparator],hlm-select-separator',
7
+ hostDirectives: [{ directive: BrnSelectSeparator, inputs: ['orientation'] }],
8
+ host: { 'data-slot': 'select-separator' },
9
+ })
10
+ export class HlmSelectSeparator {
11
+ constructor() {
12
+ classes(() => 'bg-border -mx-1 my-1 h-px pointer-events-none');
13
+ }
14
+ }