@tolle_/tolle-ui 18.2.30 → 18.3.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/esm2022/lib/alert-dialog-dynamic.component.mjs +8 -3
- package/esm2022/lib/alert-dialog.component.mjs +39 -6
- package/esm2022/lib/alert-dialog.service.mjs +38 -6
- package/esm2022/lib/bar-list.component.mjs +95 -0
- package/esm2022/lib/category-bar.component.mjs +130 -0
- package/esm2022/lib/chart-spark.component.mjs +120 -0
- package/esm2022/lib/chart.component.mjs +131 -45
- package/esm2022/lib/chart.service.mjs +33 -8
- package/esm2022/lib/context-menu.service.mjs +11 -7
- package/esm2022/lib/modal-ref.mjs +36 -7
- package/esm2022/lib/modal.component.mjs +42 -28
- package/esm2022/lib/modal.mjs +2 -29
- package/esm2022/lib/modal.service.mjs +26 -4
- package/esm2022/lib/progress-circle.component.mjs +138 -0
- package/esm2022/lib/resizable-panel.component.mjs +5 -5
- package/esm2022/lib/segment.component.mjs +3 -3
- package/esm2022/lib/tracker.component.mjs +84 -0
- package/esm2022/public-api.mjs +6 -1
- package/fesm2022/tolle-ui.mjs +900 -139
- package/fesm2022/tolle-ui.mjs.map +1 -1
- package/lib/alert-dialog-dynamic.component.d.ts +5 -0
- package/lib/alert-dialog.component.d.ts +13 -0
- package/lib/bar-list.component.d.ts +39 -0
- package/lib/category-bar.component.d.ts +49 -0
- package/lib/chart-spark.component.d.ts +49 -0
- package/lib/chart.component.d.ts +68 -9
- package/lib/chart.service.d.ts +25 -5
- package/lib/modal-ref.d.ts +6 -1
- package/lib/modal.component.d.ts +7 -4
- package/lib/modal.d.ts +9 -3
- package/lib/progress-circle.component.d.ts +35 -0
- package/lib/tracker.component.d.ts +39 -0
- package/package.json +3 -2
- package/preset.js +7 -1
- package/public-api.d.ts +5 -0
- package/registry/docs-content.json +330 -2
- package/registry/llms-full.txt +98 -2
- package/registry/llms.txt +5 -0
- package/registry/manifest.json +380 -3
- package/registry/r/alert-dialog-dynamic.json +1 -1
- package/registry/r/alert-dialog.json +1 -1
- package/registry/r/bar-list.json +21 -0
- package/registry/r/category-bar.json +21 -0
- package/registry/r/chart-pie.json +1 -1
- package/registry/r/chart-spark.json +30 -0
- package/registry/r/chart.json +2 -2
- package/registry/r/context-menu-trigger.json +1 -1
- package/registry/r/context-menu.json +1 -1
- package/registry/r/modal.json +3 -3
- package/registry/r/progress-circle.json +21 -0
- package/registry/r/resizable-panel.json +1 -1
- package/registry/r/segment.json +1 -1
- package/registry/r/tracker.json +25 -0
- package/registry/registry.json +112 -0
- package/theme.css +15 -3
package/registry/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"package": "@tolle_/tolle-ui",
|
|
3
|
-
"version": "18.
|
|
3
|
+
"version": "18.3.1",
|
|
4
4
|
"registry": "r/{name}.json",
|
|
5
5
|
"components": [
|
|
6
6
|
{
|
|
@@ -860,6 +860,62 @@
|
|
|
860
860
|
}
|
|
861
861
|
]
|
|
862
862
|
},
|
|
863
|
+
{
|
|
864
|
+
"name": "bar-list",
|
|
865
|
+
"title": "Bar List",
|
|
866
|
+
"description": "Bar List component.",
|
|
867
|
+
"category": "components",
|
|
868
|
+
"selector": "tolle-bar-list",
|
|
869
|
+
"isNew": true,
|
|
870
|
+
"import": "import { BarListComponent } from '@tolle_/tolle-ui';",
|
|
871
|
+
"install": "npx @tolle_/cli add bar-list",
|
|
872
|
+
"registryDependencies": [],
|
|
873
|
+
"dependencies": [],
|
|
874
|
+
"files": [
|
|
875
|
+
"bar-list.component.ts",
|
|
876
|
+
"utils/cn.ts"
|
|
877
|
+
],
|
|
878
|
+
"components": [
|
|
879
|
+
{
|
|
880
|
+
"className": "BarListComponent",
|
|
881
|
+
"selector": "tolle-bar-list",
|
|
882
|
+
"kind": "component",
|
|
883
|
+
"inputs": [
|
|
884
|
+
{
|
|
885
|
+
"name": "data",
|
|
886
|
+
"type": "Record<string, any>[]",
|
|
887
|
+
"default": "[]",
|
|
888
|
+
"description": "Rows to rank, in the order they should be drawn.",
|
|
889
|
+
"required": false
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
"name": "labelKey",
|
|
893
|
+
"type": "string",
|
|
894
|
+
"default": "'label'",
|
|
895
|
+
"description": "Row key holding each row's label.",
|
|
896
|
+
"required": false
|
|
897
|
+
},
|
|
898
|
+
{
|
|
899
|
+
"name": "valueKey",
|
|
900
|
+
"type": "string",
|
|
901
|
+
"default": "'value'",
|
|
902
|
+
"description": "Row key holding each row's numeric value.",
|
|
903
|
+
"required": false
|
|
904
|
+
},
|
|
905
|
+
{
|
|
906
|
+
"name": "class",
|
|
907
|
+
"type": "string",
|
|
908
|
+
"default": "''",
|
|
909
|
+
"description": "Extra Tailwind classes merged onto the list via `cn()` (last-wins).",
|
|
910
|
+
"required": false
|
|
911
|
+
}
|
|
912
|
+
],
|
|
913
|
+
"outputs": [],
|
|
914
|
+
"slots": [],
|
|
915
|
+
"variants": []
|
|
916
|
+
}
|
|
917
|
+
]
|
|
918
|
+
},
|
|
863
919
|
{
|
|
864
920
|
"name": "breadcrumb",
|
|
865
921
|
"title": "Breadcrumb",
|
|
@@ -1671,6 +1727,76 @@
|
|
|
1671
1727
|
}
|
|
1672
1728
|
]
|
|
1673
1729
|
},
|
|
1730
|
+
{
|
|
1731
|
+
"name": "category-bar",
|
|
1732
|
+
"title": "Category Bar",
|
|
1733
|
+
"description": "Category Bar component.",
|
|
1734
|
+
"category": "components",
|
|
1735
|
+
"selector": "tolle-category-bar",
|
|
1736
|
+
"isNew": true,
|
|
1737
|
+
"import": "import { CategoryBarComponent } from '@tolle_/tolle-ui';",
|
|
1738
|
+
"install": "npx @tolle_/cli add category-bar",
|
|
1739
|
+
"registryDependencies": [],
|
|
1740
|
+
"dependencies": [],
|
|
1741
|
+
"files": [
|
|
1742
|
+
"category-bar.component.ts",
|
|
1743
|
+
"utils/cn.ts"
|
|
1744
|
+
],
|
|
1745
|
+
"components": [
|
|
1746
|
+
{
|
|
1747
|
+
"className": "CategoryBarComponent",
|
|
1748
|
+
"selector": "tolle-category-bar",
|
|
1749
|
+
"kind": "component",
|
|
1750
|
+
"inputs": [
|
|
1751
|
+
{
|
|
1752
|
+
"name": "values",
|
|
1753
|
+
"type": "number[]",
|
|
1754
|
+
"default": "[]",
|
|
1755
|
+
"description": "Width of each segment. Values that are not finite and greater than zero are dropped.",
|
|
1756
|
+
"required": false
|
|
1757
|
+
},
|
|
1758
|
+
{
|
|
1759
|
+
"name": "labels",
|
|
1760
|
+
"type": "string[]",
|
|
1761
|
+
"default": "[]",
|
|
1762
|
+
"description": "Name for each segment, used in the accessible summary and as a title tooltip.",
|
|
1763
|
+
"required": false
|
|
1764
|
+
},
|
|
1765
|
+
{
|
|
1766
|
+
"name": "colors",
|
|
1767
|
+
"type": "string[]",
|
|
1768
|
+
"default": "[]",
|
|
1769
|
+
"description": "Paint for each segment, by index. Falls back to the chart palette --chart-1 through --chart-5.",
|
|
1770
|
+
"required": false
|
|
1771
|
+
},
|
|
1772
|
+
{
|
|
1773
|
+
"name": "markerValue",
|
|
1774
|
+
"type": "number | null",
|
|
1775
|
+
"default": "null",
|
|
1776
|
+
"description": "Position of the pointer, 0-100 on the same scale the segments sum to. Omit to draw no pointer.",
|
|
1777
|
+
"required": false
|
|
1778
|
+
},
|
|
1779
|
+
{
|
|
1780
|
+
"name": "ariaLabel",
|
|
1781
|
+
"type": "string",
|
|
1782
|
+
"default": "''",
|
|
1783
|
+
"description": "Accessible name, prefixed onto the sr-only summary of segments and marker.",
|
|
1784
|
+
"required": false
|
|
1785
|
+
},
|
|
1786
|
+
{
|
|
1787
|
+
"name": "class",
|
|
1788
|
+
"type": "string",
|
|
1789
|
+
"default": "''",
|
|
1790
|
+
"description": "Extra Tailwind classes merged onto the bar via `cn()` (last-wins).",
|
|
1791
|
+
"required": false
|
|
1792
|
+
}
|
|
1793
|
+
],
|
|
1794
|
+
"outputs": [],
|
|
1795
|
+
"slots": [],
|
|
1796
|
+
"variants": []
|
|
1797
|
+
}
|
|
1798
|
+
]
|
|
1799
|
+
},
|
|
1674
1800
|
{
|
|
1675
1801
|
"name": "chain-of-thought",
|
|
1676
1802
|
"title": "Chain Of Thought",
|
|
@@ -2094,6 +2220,13 @@
|
|
|
2094
2220
|
"description": "Stacks marks on a shared baseline instead of grouping them.",
|
|
2095
2221
|
"required": false
|
|
2096
2222
|
},
|
|
2223
|
+
{
|
|
2224
|
+
"name": "orientation",
|
|
2225
|
+
"type": "ChartOrientation",
|
|
2226
|
+
"default": "'vertical'",
|
|
2227
|
+
"description": "Which physical axis carries the category vs the value. Only bars support\n'horizontal' — line/area marks stay vertical regardless.",
|
|
2228
|
+
"required": false
|
|
2229
|
+
},
|
|
2097
2230
|
{
|
|
2098
2231
|
"name": "hover",
|
|
2099
2232
|
"type": "boolean",
|
|
@@ -2177,7 +2310,7 @@
|
|
|
2177
2310
|
"name": "vertical",
|
|
2178
2311
|
"type": "boolean",
|
|
2179
2312
|
"default": "false",
|
|
2180
|
-
"description": "Also draws a rule at every
|
|
2313
|
+
"description": "Also draws a rule at every category position.",
|
|
2181
2314
|
"required": false
|
|
2182
2315
|
},
|
|
2183
2316
|
{
|
|
@@ -2201,7 +2334,7 @@
|
|
|
2201
2334
|
"name": "charWidth",
|
|
2202
2335
|
"type": "number",
|
|
2203
2336
|
"default": "7",
|
|
2204
|
-
"description": "Approximate px per character, used to decide when labels would
|
|
2337
|
+
"description": "Approximate px per character, used to decide when category labels would\ncollide. Only consulted in the vertical orientation.",
|
|
2205
2338
|
"required": false
|
|
2206
2339
|
},
|
|
2207
2340
|
{
|
|
@@ -2516,6 +2649,130 @@
|
|
|
2516
2649
|
}
|
|
2517
2650
|
]
|
|
2518
2651
|
},
|
|
2652
|
+
{
|
|
2653
|
+
"name": "chart-spark",
|
|
2654
|
+
"title": "Chart Spark",
|
|
2655
|
+
"description": "Chart Spark component.",
|
|
2656
|
+
"category": "data",
|
|
2657
|
+
"selector": "tolle-chart-spark",
|
|
2658
|
+
"isNew": true,
|
|
2659
|
+
"import": "import { ChartSparkComponent } from '@tolle_/tolle-ui';",
|
|
2660
|
+
"install": "npx @tolle_/cli add chart-spark",
|
|
2661
|
+
"registryDependencies": [
|
|
2662
|
+
"chart"
|
|
2663
|
+
],
|
|
2664
|
+
"dependencies": [
|
|
2665
|
+
"class-variance-authority"
|
|
2666
|
+
],
|
|
2667
|
+
"files": [
|
|
2668
|
+
"chart-spark.component.ts",
|
|
2669
|
+
"chart.service.ts",
|
|
2670
|
+
"utils/cn.ts"
|
|
2671
|
+
],
|
|
2672
|
+
"components": [
|
|
2673
|
+
{
|
|
2674
|
+
"className": "ChartSparkComponent",
|
|
2675
|
+
"selector": "tolle-chart-spark",
|
|
2676
|
+
"kind": "component",
|
|
2677
|
+
"inputs": [
|
|
2678
|
+
{
|
|
2679
|
+
"name": "data",
|
|
2680
|
+
"type": "Record<string, any>[]",
|
|
2681
|
+
"default": "[]",
|
|
2682
|
+
"description": "Rows to plot, in x order.",
|
|
2683
|
+
"required": false
|
|
2684
|
+
},
|
|
2685
|
+
{
|
|
2686
|
+
"name": "valueKey",
|
|
2687
|
+
"type": "string",
|
|
2688
|
+
"default": "'value'",
|
|
2689
|
+
"description": "Row key holding each row's numeric value.",
|
|
2690
|
+
"required": false
|
|
2691
|
+
},
|
|
2692
|
+
{
|
|
2693
|
+
"name": "xKey",
|
|
2694
|
+
"type": "string",
|
|
2695
|
+
"default": "''",
|
|
2696
|
+
"description": "Row key holding each row's x position. Only matters once `hover` is on.",
|
|
2697
|
+
"required": false
|
|
2698
|
+
},
|
|
2699
|
+
{
|
|
2700
|
+
"name": "type",
|
|
2701
|
+
"type": "'line' | 'area' | 'bar'",
|
|
2702
|
+
"default": "'line'",
|
|
2703
|
+
"description": "Which mark to draw.",
|
|
2704
|
+
"required": false
|
|
2705
|
+
},
|
|
2706
|
+
{
|
|
2707
|
+
"name": "curve",
|
|
2708
|
+
"type": "'linear' | 'smooth'",
|
|
2709
|
+
"default": "'linear'",
|
|
2710
|
+
"description": "Interpolation for line/area.",
|
|
2711
|
+
"required": false
|
|
2712
|
+
},
|
|
2713
|
+
{
|
|
2714
|
+
"name": "maxBarWidth",
|
|
2715
|
+
"type": "number",
|
|
2716
|
+
"default": "6",
|
|
2717
|
+
"description": "Largest bar thickness in px, used when `type` is 'bar'.",
|
|
2718
|
+
"required": false
|
|
2719
|
+
},
|
|
2720
|
+
{
|
|
2721
|
+
"name": "height",
|
|
2722
|
+
"type": "number",
|
|
2723
|
+
"default": "32",
|
|
2724
|
+
"description": "Height in px.",
|
|
2725
|
+
"required": false
|
|
2726
|
+
},
|
|
2727
|
+
{
|
|
2728
|
+
"name": "hover",
|
|
2729
|
+
"type": "boolean",
|
|
2730
|
+
"default": "false",
|
|
2731
|
+
"description": "Renders the crosshair, hit layer and tooltip.",
|
|
2732
|
+
"required": false
|
|
2733
|
+
},
|
|
2734
|
+
{
|
|
2735
|
+
"name": "showTable",
|
|
2736
|
+
"type": "boolean",
|
|
2737
|
+
"default": "false",
|
|
2738
|
+
"description": "Shows the data table instead of leaving it visually hidden.",
|
|
2739
|
+
"required": false
|
|
2740
|
+
},
|
|
2741
|
+
{
|
|
2742
|
+
"name": "ariaLabel",
|
|
2743
|
+
"type": "string",
|
|
2744
|
+
"default": "''",
|
|
2745
|
+
"description": "Accessible name, used as the svg <title>.",
|
|
2746
|
+
"required": false
|
|
2747
|
+
},
|
|
2748
|
+
{
|
|
2749
|
+
"name": "description",
|
|
2750
|
+
"type": "string",
|
|
2751
|
+
"default": "''",
|
|
2752
|
+
"description": "Longer summary used as the svg <desc>. Falls back to `ariaLabel`.",
|
|
2753
|
+
"required": false
|
|
2754
|
+
},
|
|
2755
|
+
{
|
|
2756
|
+
"name": "margin",
|
|
2757
|
+
"type": "ChartMargin",
|
|
2758
|
+
"default": "{ top: 2, right: 2, bottom: 2, left: 2 }",
|
|
2759
|
+
"description": "Space reserved around the plot — a sparkline has no axis labels to fit.",
|
|
2760
|
+
"required": false
|
|
2761
|
+
},
|
|
2762
|
+
{
|
|
2763
|
+
"name": "class",
|
|
2764
|
+
"type": "string",
|
|
2765
|
+
"default": "''",
|
|
2766
|
+
"description": "Extra Tailwind classes merged onto the chart via `cn()` (last-wins).",
|
|
2767
|
+
"required": false
|
|
2768
|
+
}
|
|
2769
|
+
],
|
|
2770
|
+
"outputs": [],
|
|
2771
|
+
"slots": [],
|
|
2772
|
+
"variants": []
|
|
2773
|
+
}
|
|
2774
|
+
]
|
|
2775
|
+
},
|
|
2519
2776
|
{
|
|
2520
2777
|
"name": "checkbox",
|
|
2521
2778
|
"title": "Checkbox",
|
|
@@ -8501,6 +8758,66 @@
|
|
|
8501
8758
|
}
|
|
8502
8759
|
]
|
|
8503
8760
|
},
|
|
8761
|
+
{
|
|
8762
|
+
"name": "progress-circle",
|
|
8763
|
+
"title": "Progress Circle",
|
|
8764
|
+
"description": "Progress Circle component.",
|
|
8765
|
+
"category": "feedback",
|
|
8766
|
+
"selector": "tolle-progress-circle",
|
|
8767
|
+
"isNew": true,
|
|
8768
|
+
"import": "import { ProgressCircleComponent } from '@tolle_/tolle-ui';",
|
|
8769
|
+
"install": "npx @tolle_/cli add progress-circle",
|
|
8770
|
+
"registryDependencies": [],
|
|
8771
|
+
"dependencies": [],
|
|
8772
|
+
"files": [
|
|
8773
|
+
"progress-circle.component.ts",
|
|
8774
|
+
"utils/cn.ts"
|
|
8775
|
+
],
|
|
8776
|
+
"components": [
|
|
8777
|
+
{
|
|
8778
|
+
"className": "ProgressCircleComponent",
|
|
8779
|
+
"selector": "tolle-progress-circle",
|
|
8780
|
+
"kind": "component",
|
|
8781
|
+
"inputs": [
|
|
8782
|
+
{
|
|
8783
|
+
"name": "value",
|
|
8784
|
+
"type": "number | null",
|
|
8785
|
+
"default": "0",
|
|
8786
|
+
"description": "Percent complete, 0-100.",
|
|
8787
|
+
"required": false
|
|
8788
|
+
},
|
|
8789
|
+
{
|
|
8790
|
+
"name": "size",
|
|
8791
|
+
"type": "number",
|
|
8792
|
+
"default": "120",
|
|
8793
|
+
"description": "Diameter of the ring in px.",
|
|
8794
|
+
"required": false
|
|
8795
|
+
},
|
|
8796
|
+
{
|
|
8797
|
+
"name": "strokeWidth",
|
|
8798
|
+
"type": "number",
|
|
8799
|
+
"default": "8",
|
|
8800
|
+
"description": "Thickness of the ring in px.",
|
|
8801
|
+
"required": false
|
|
8802
|
+
},
|
|
8803
|
+
{
|
|
8804
|
+
"name": "class",
|
|
8805
|
+
"type": "string",
|
|
8806
|
+
"default": "''",
|
|
8807
|
+
"description": "Extra Tailwind classes merged onto the ring via `cn()` (last-wins).",
|
|
8808
|
+
"required": false
|
|
8809
|
+
}
|
|
8810
|
+
],
|
|
8811
|
+
"outputs": [],
|
|
8812
|
+
"slots": [
|
|
8813
|
+
{
|
|
8814
|
+
"name": ""
|
|
8815
|
+
}
|
|
8816
|
+
],
|
|
8817
|
+
"variants": []
|
|
8818
|
+
}
|
|
8819
|
+
]
|
|
8820
|
+
},
|
|
8504
8821
|
{
|
|
8505
8822
|
"name": "prompt-input",
|
|
8506
8823
|
"title": "Prompt Input",
|
|
@@ -12209,6 +12526,66 @@
|
|
|
12209
12526
|
}
|
|
12210
12527
|
]
|
|
12211
12528
|
},
|
|
12529
|
+
{
|
|
12530
|
+
"name": "tracker",
|
|
12531
|
+
"title": "Tracker",
|
|
12532
|
+
"description": "Tracker component.",
|
|
12533
|
+
"category": "components",
|
|
12534
|
+
"selector": "tolle-tracker",
|
|
12535
|
+
"isNew": true,
|
|
12536
|
+
"import": "import { TrackerComponent } from '@tolle_/tolle-ui';",
|
|
12537
|
+
"install": "npx @tolle_/cli add tracker",
|
|
12538
|
+
"registryDependencies": [
|
|
12539
|
+
"tooltip"
|
|
12540
|
+
],
|
|
12541
|
+
"dependencies": [
|
|
12542
|
+
"@floating-ui/dom"
|
|
12543
|
+
],
|
|
12544
|
+
"files": [
|
|
12545
|
+
"tracker.component.ts",
|
|
12546
|
+
"utils/cn.ts"
|
|
12547
|
+
],
|
|
12548
|
+
"components": [
|
|
12549
|
+
{
|
|
12550
|
+
"className": "TrackerComponent",
|
|
12551
|
+
"selector": "tolle-tracker",
|
|
12552
|
+
"kind": "component",
|
|
12553
|
+
"inputs": [
|
|
12554
|
+
{
|
|
12555
|
+
"name": "data",
|
|
12556
|
+
"type": "TrackerBlock[]",
|
|
12557
|
+
"default": "[]",
|
|
12558
|
+
"description": "One block per period, in chronological order.",
|
|
12559
|
+
"required": false
|
|
12560
|
+
},
|
|
12561
|
+
{
|
|
12562
|
+
"name": "blockHeight",
|
|
12563
|
+
"type": "number",
|
|
12564
|
+
"default": "32",
|
|
12565
|
+
"description": "Height of each block in px.",
|
|
12566
|
+
"required": false
|
|
12567
|
+
},
|
|
12568
|
+
{
|
|
12569
|
+
"name": "ariaLabel",
|
|
12570
|
+
"type": "string",
|
|
12571
|
+
"default": "''",
|
|
12572
|
+
"description": "Accessible name for the whole strip.",
|
|
12573
|
+
"required": false
|
|
12574
|
+
},
|
|
12575
|
+
{
|
|
12576
|
+
"name": "class",
|
|
12577
|
+
"type": "string",
|
|
12578
|
+
"default": "''",
|
|
12579
|
+
"description": "Extra Tailwind classes merged onto the strip via `cn()` (last-wins).",
|
|
12580
|
+
"required": false
|
|
12581
|
+
}
|
|
12582
|
+
],
|
|
12583
|
+
"outputs": [],
|
|
12584
|
+
"slots": [],
|
|
12585
|
+
"variants": []
|
|
12586
|
+
}
|
|
12587
|
+
]
|
|
12588
|
+
},
|
|
12212
12589
|
{
|
|
12213
12590
|
"name": "typography",
|
|
12214
12591
|
"title": "Typography",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
{
|
|
16
16
|
"path": "alert-dialog-dynamic.component.ts",
|
|
17
17
|
"target": "ui/alert-dialog-dynamic.component.ts",
|
|
18
|
-
"content": "import { Component, Inject, inject, OnInit } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport {\n AlertDialogComponent,\n AlertDialogContentComponent,\n AlertDialogHeaderComponent,\n AlertDialogTitleComponent,\n AlertDialogDescriptionComponent,\n AlertDialogFooterComponent,\n AlertDialogCancelComponent,\n AlertDialogActionComponent\n} from './alert-dialog.component';\nimport { ButtonComponent } from './button.component';\nimport { AlertDialogConfig, AlertDialogRef } from './alert-dialog.types';\n\n@Component({\n selector: 'tolle-alert-dialog-dynamic',\n styles: [':host { display: block; }'],\n standalone: true,\n imports: [\n CommonModule,\n AlertDialogComponent,\n AlertDialogContentComponent,\n AlertDialogHeaderComponent,\n AlertDialogTitleComponent,\n AlertDialogDescriptionComponent,\n AlertDialogFooterComponent,\n AlertDialogCancelComponent,\n AlertDialogActionComponent,\n ButtonComponent\n ],\n template: `\n <tolle-alert-dialog [open]=\"
|
|
18
|
+
"content": "import { Component, Inject, inject, OnInit } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport {\n AlertDialogComponent,\n AlertDialogContentComponent,\n AlertDialogHeaderComponent,\n AlertDialogTitleComponent,\n AlertDialogDescriptionComponent,\n AlertDialogFooterComponent,\n AlertDialogCancelComponent,\n AlertDialogActionComponent\n} from './alert-dialog.component';\nimport { ButtonComponent } from './button.component';\nimport { AlertDialogConfig, AlertDialogRef } from './alert-dialog.types';\n\n@Component({\n selector: 'tolle-alert-dialog-dynamic',\n styles: [':host { display: block; }'],\n standalone: true,\n imports: [\n CommonModule,\n AlertDialogComponent,\n AlertDialogContentComponent,\n AlertDialogHeaderComponent,\n AlertDialogTitleComponent,\n AlertDialogDescriptionComponent,\n AlertDialogFooterComponent,\n AlertDialogCancelComponent,\n AlertDialogActionComponent,\n ButtonComponent\n ],\n template: `\n <tolle-alert-dialog [open]=\"!closing\" (openChange)=\"onOpenChange($event)\">\n <tolle-alert-dialog-content [size]=\"config.size || 'lg'\">\n <tolle-alert-dialog-header>\n <tolle-alert-dialog-title>{{ config.title }}</tolle-alert-dialog-title>\n <tolle-alert-dialog-description>\n {{ config.description }}\n </tolle-alert-dialog-description>\n </tolle-alert-dialog-header>\n <tolle-alert-dialog-footer>\n <tolle-alert-dialog-cancel (click)=\"close(false)\">\n <tolle-button variant=\"outline\">{{ config.cancelText || 'Cancel' }}</tolle-button>\n </tolle-alert-dialog-cancel>\n <tolle-alert-dialog-action (click)=\"close(true)\">\n <tolle-button [variant]=\"config.variant === 'destructive' ? 'destructive' : 'default'\">\n {{ config.actionText || 'Continue' }}\n </tolle-button>\n </tolle-alert-dialog-action>\n </tolle-alert-dialog-footer>\n </tolle-alert-dialog-content>\n </tolle-alert-dialog>\n `\n})\nexport class AlertDialogDynamicComponent {\n config!: AlertDialogConfig;\n dialogRef!: AlertDialogRef;\n\n /**\n * Set by `AlertDialogService` once the dialog's result is ready, so the\n * content panel plays its exit animation before the overlay is torn down.\n */\n closing = false;\n\n onOpenChange(open: boolean) {\n if (!open) {\n this.close(false);\n }\n }\n\n close(result: boolean) {\n this.dialogRef.close(result);\n }\n}\n"
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
"path": "alert-dialog.types.ts",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
{
|
|
11
11
|
"path": "alert-dialog.component.ts",
|
|
12
12
|
"target": "ui/alert-dialog.component.ts",
|
|
13
|
-
"content": "import { Component, Input, Output, EventEmitter, HostListener, Injectable, inject, TemplateRef, ViewChild, ViewContainerRef, OnDestroy, OnInit, ContentChild, forwardRef } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { Overlay, OverlayRef, OverlayConfig } from '@angular/cdk/overlay';\nimport { TemplatePortal, ComponentPortal } from '@angular/cdk/portal';\nimport { A11yModule } from '@angular/cdk/a11y';\nimport { cn } from './utils/cn';\nimport { BehaviorSubject } from 'rxjs';\nimport { AlertDialogSize } from './alert-dialog.types';\n\n@Injectable()\nclass AlertDialogInternalService {\n private openSubject = new BehaviorSubject<boolean>(false);\n open$ = this.openSubject.asObservable();\n\n setOpen(value: boolean) {\n this.openSubject.next(value);\n }\n\n getOpen() {\n return this.openSubject.getValue();\n }\n\n toggle() {\n this.setOpen(!this.getOpen());\n }\n}\n\n@Component({\n selector: 'tolle-alert-dialog',\n styles: [':host { display: block; }'],\n standalone: true,\n imports: [CommonModule],\n providers: [AlertDialogInternalService],\n template: `<ng-content></ng-content>`\n})\nexport class AlertDialogComponent {\n @Input() set open(val: boolean) {\n this.alertDialogService.setOpen(val);\n }\n @Output() openChange = new EventEmitter<boolean>();\n\n private alertDialogService = inject(AlertDialogInternalService);\n\n constructor() {\n this.alertDialogService.open$.subscribe(val => {\n this.openChange.emit(val);\n });\n }\n}\n\n@Component({\n selector: 'tolle-alert-dialog-trigger',\n styles: [':host { display: block; }'],\n standalone: true,\n imports: [CommonModule],\n template: `<ng-content></ng-content>`,\n host: {\n '(click)': 'onOpen()'\n }\n})\nexport class AlertDialogTriggerComponent {\n private alertDialogService = inject(AlertDialogInternalService);\n\n onOpen() {\n this.alertDialogService.setOpen(true);\n }\n}\n\n@Component({\n selector: 'tolle-alert-dialog-portal',\n styles: [':host { display: block; }'],\n standalone: true,\n imports: [CommonModule],\n template: `\n <ng-template #portalContent>\n <ng-content></ng-content>\n </ng-template>\n `\n})\nexport class AlertDialogPortalComponent implements OnInit, OnDestroy {\n @ViewChild('portalContent', { static: true }) portalContent!: TemplateRef<any>;\n private alertDialogService = inject(AlertDialogInternalService);\n private overlay = inject(Overlay);\n private viewContainerRef = inject(ViewContainerRef);\n private overlayRef?: OverlayRef;\n /** Element focused before the dialog opened, restored on close. */\n private previouslyFocused?: HTMLElement | null;\n\n ngOnInit() {\n this.alertDialogService.open$.subscribe(open => {\n if (open) {\n this.show();\n } else {\n this.hide();\n }\n });\n }\n\n private show() {\n if (this.overlayRef) return;\n\n // Remember the trigger so focus can be restored on close.\n this.previouslyFocused = document.activeElement as HTMLElement | null;\n\n const config = new OverlayConfig({\n hasBackdrop: true,\n backdropClass: ['cdk-overlay-backdrop', 'bg-black/80', 'backdrop-blur-sm', 'data-[state=open]:animate-in', 'data-[state=closed]:animate-out', 'data-[state=closed]:fade-out-0', 'data-[state=open]:fade-in-0'],\n positionStrategy: this.overlay.position().global().centerHorizontally().centerVertically(),\n scrollStrategy: this.overlay.scrollStrategies.block()\n });\n\n this.overlayRef = this.overlay.create(config);\n\n // Alert-dialog semantics: backdrop clicks must NOT dismiss.\n // Escape still closes the dialog.\n this.overlayRef.keydownEvents().subscribe((event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n event.preventDefault();\n this.alertDialogService.setOpen(false);\n }\n });\n\n const portal = new TemplatePortal(this.portalContent, this.viewContainerRef);\n this.overlayRef.attach(portal);\n }\n\n private hide() {\n if (this.overlayRef) {\n this.overlayRef.detach();\n this.overlayRef.dispose();\n this.overlayRef = undefined;\n // Restore focus to the trigger.\n this.previouslyFocused?.focus?.();\n }\n }\n\n ngOnDestroy() {\n this.hide();\n }\n}\n\n@Component({\n selector: 'tolle-alert-dialog-content',\n standalone: true,\n imports: [CommonModule, A11yModule],\n template: `<div [class]=\"computedClass\" [attr.data-state]=\"isOpen ? 'open' : 'closed'\"\n role=\"alertdialog\"\n aria-modal=\"true\"\n [attr.aria-labelledby]=\"titleCmp?.id || null\"\n [attr.aria-describedby]=\"descriptionCmp?.id || null\"\n cdkTrapFocus cdkTrapFocusAutoCapture><ng-content></ng-content></div>`,\n host: {\n '[class]': '\"contents\"'\n }\n})\nexport class AlertDialogContentComponent {\n @Input() class: string = '';\n @Input() size: AlertDialogSize = 'lg';\n\n /** Projected title/description used to build the dialog's accessible name. */\n @ContentChild(forwardRef(() => AlertDialogTitleComponent)) titleCmp?: AlertDialogTitleComponent;\n @ContentChild(forwardRef(() => AlertDialogDescriptionComponent)) descriptionCmp?: AlertDialogDescriptionComponent;\n\n private alertDialogService = inject(AlertDialogInternalService);\n isOpen = false;\n\n constructor() {\n this.alertDialogService.open$.subscribe(val => {\n this.isOpen = val;\n });\n }\n\n get computedClass() {\n return cn(\n \"fixed left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] z-50 grid w-full gap-4 border border-input bg-background p-6 shadow-lg data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] rounded-lg\",\n {\n 'max-w-xs': this.size === 'xs',\n 'max-w-sm': this.size === 'sm',\n 'max-w-md': this.size === 'md',\n 'max-w-lg': this.size === 'lg',\n 'max-w-xl': this.size === 'xl',\n 'max-w-2xl': this.size === '2xl',\n 'max-w-full': this.size === 'full',\n 'max-w-fit': this.size === 'fit',\n },\n this.class\n );\n }\n}\n\n@Component({\n selector: 'tolle-alert-dialog-header',\n standalone: true,\n imports: [CommonModule],\n template: `<ng-content></ng-content>`,\n host: { '[class]': 'computedClass' }\n})\nexport class AlertDialogHeaderComponent {\n @Input() class: string = '';\n get computedClass() { return cn(\"flex flex-col space-y-2 text-center sm:text-left\", this.class); }\n}\n\n@Component({\n selector: 'tolle-alert-dialog-footer',\n standalone: true,\n imports: [CommonModule],\n template: `<ng-content></ng-content>`,\n host: { '[class]': 'computedClass' }\n})\nexport class AlertDialogFooterComponent {\n @Input() class: string = '';\n get computedClass() { return cn(\"flex flex-row justify-end space-x-2\", this.class); }\n}\n\n@Component({\n selector: 'tolle-alert-dialog-title',\n styles: [':host { display: block; }'],\n standalone: true,\n imports: [CommonModule],\n template: `<ng-content></ng-content>`,\n host: { '[class]': 'computedClass', '[attr.id]': 'id' }\n})\nexport class AlertDialogTitleComponent {\n @Input() class: string = '';\n /** Stable id referenced by the dialog's `aria-labelledby`. */\n readonly id = `tolle-alert-dialog-title-${Math.random().toString(36).substr(2, 9)}`;\n get computedClass() { return cn(\"text-lg font-semibold text-foreground\", this.class); }\n}\n\n@Component({\n selector: 'tolle-alert-dialog-description',\n styles: [':host { display: block; }'],\n standalone: true,\n imports: [CommonModule],\n template: `<ng-content></ng-content>`,\n host: { '[class]': 'computedClass', '[attr.id]': 'id' }\n})\nexport class AlertDialogDescriptionComponent {\n @Input() class: string = '';\n /** Stable id referenced by the dialog's `aria-describedby`. */\n readonly id = `tolle-alert-dialog-description-${Math.random().toString(36).substr(2, 9)}`;\n get computedClass() { return cn(\"text-sm text-muted-foreground\", this.class); }\n}\n\n@Component({\n selector: 'tolle-alert-dialog-action',\n styles: [':host { display: block; }'],\n standalone: true,\n imports: [CommonModule],\n template: `<ng-content></ng-content>`\n})\nexport class AlertDialogActionComponent {\n private alertDialogService = inject(AlertDialogInternalService, { optional: true });\n\n /** Fires before the dialog closes, so consumers can run the confirmed action. */\n @Output() confirmed = new EventEmitter<MouseEvent>();\n\n @HostListener('click', ['$event'])\n handleClick(event: MouseEvent) {\n this.confirmed.emit(event);\n this.alertDialogService?.setOpen(false);\n }\n}\n\n@Component({\n selector: 'tolle-alert-dialog-cancel',\n styles: [':host { display: block; }'],\n standalone: true,\n imports: [CommonModule],\n template: `<ng-content></ng-content>`\n})\nexport class AlertDialogCancelComponent {\n private alertDialogService = inject(AlertDialogInternalService, { optional: true });\n\n /** Fires before the dialog closes, so consumers can react to a dismissal. */\n @Output() cancelled = new EventEmitter<MouseEvent>();\n\n @HostListener('click', ['$event'])\n handleClick(event: MouseEvent) {\n this.cancelled.emit(event);\n this.alertDialogService?.setOpen(false);\n }\n}\n"
|
|
13
|
+
"content": "import { Component, Input, Output, EventEmitter, HostListener, Injectable, inject, TemplateRef, ViewChild, ViewContainerRef, OnDestroy, OnInit, ContentChild, forwardRef } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { Overlay, OverlayRef, OverlayConfig } from '@angular/cdk/overlay';\nimport { TemplatePortal, ComponentPortal } from '@angular/cdk/portal';\nimport { A11yModule } from '@angular/cdk/a11y';\nimport { cn } from './utils/cn';\nimport { BehaviorSubject } from 'rxjs';\nimport { AlertDialogSize } from './alert-dialog.types';\n\n/**\n * Safety net if the exit animation's `animationend` never fires — reduced\n * motion, no matching keyframe, or a panel that never rendered — so an\n * alert dialog can never get stuck open forever. Also used by\n * `alert-dialog.service.ts` and `alert-dialog-dynamic.component.ts`, which\n * animate the same content component through a different overlay wiring.\n */\nexport const CLOSE_FALLBACK_MS = 300;\n\n@Injectable()\nclass AlertDialogInternalService {\n private openSubject = new BehaviorSubject<boolean>(false);\n open$ = this.openSubject.asObservable();\n\n setOpen(value: boolean) {\n this.openSubject.next(value);\n }\n\n getOpen() {\n return this.openSubject.getValue();\n }\n\n toggle() {\n this.setOpen(!this.getOpen());\n }\n}\n\n@Component({\n selector: 'tolle-alert-dialog',\n styles: [':host { display: block; }'],\n standalone: true,\n imports: [CommonModule],\n providers: [AlertDialogInternalService],\n template: `<ng-content></ng-content>`\n})\nexport class AlertDialogComponent {\n @Input() set open(val: boolean) {\n this.alertDialogService.setOpen(val);\n }\n @Output() openChange = new EventEmitter<boolean>();\n\n private alertDialogService = inject(AlertDialogInternalService);\n\n constructor() {\n this.alertDialogService.open$.subscribe(val => {\n this.openChange.emit(val);\n });\n }\n}\n\n@Component({\n selector: 'tolle-alert-dialog-trigger',\n styles: [':host { display: block; }'],\n standalone: true,\n imports: [CommonModule],\n template: `<ng-content></ng-content>`,\n host: {\n '(click)': 'onOpen()'\n }\n})\nexport class AlertDialogTriggerComponent {\n private alertDialogService = inject(AlertDialogInternalService);\n\n onOpen() {\n this.alertDialogService.setOpen(true);\n }\n}\n\n@Component({\n selector: 'tolle-alert-dialog-portal',\n styles: [':host { display: block; }'],\n standalone: true,\n imports: [CommonModule],\n template: `\n <ng-template #portalContent>\n <ng-content></ng-content>\n </ng-template>\n `\n})\nexport class AlertDialogPortalComponent implements OnInit, OnDestroy {\n @ViewChild('portalContent', { static: true }) portalContent!: TemplateRef<any>;\n private alertDialogService = inject(AlertDialogInternalService);\n private overlay = inject(Overlay);\n private viewContainerRef = inject(ViewContainerRef);\n private overlayRef?: OverlayRef;\n /** Element focused before the dialog opened, restored on close. */\n private previouslyFocused?: HTMLElement | null;\n\n ngOnInit() {\n this.alertDialogService.open$.subscribe(open => {\n if (open) {\n this.show();\n } else {\n this.hide();\n }\n });\n }\n\n private show() {\n if (this.overlayRef) return;\n\n // Remember the trigger so focus can be restored on close.\n this.previouslyFocused = document.activeElement as HTMLElement | null;\n\n const config = new OverlayConfig({\n hasBackdrop: true,\n backdropClass: ['cdk-overlay-backdrop', 'bg-black/80', 'backdrop-blur-sm', 'data-[state=open]:animate-in', 'data-[state=closed]:animate-out', 'data-[state=closed]:fade-out-0', 'data-[state=open]:fade-in-0'],\n positionStrategy: this.overlay.position().global().centerHorizontally().centerVertically(),\n scrollStrategy: this.overlay.scrollStrategies.block()\n });\n\n this.overlayRef = this.overlay.create(config);\n\n // Alert-dialog semantics: backdrop clicks must NOT dismiss.\n // Escape still closes the dialog.\n this.overlayRef.keydownEvents().subscribe((event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n event.preventDefault();\n this.alertDialogService.setOpen(false);\n }\n });\n\n const portal = new TemplatePortal(this.portalContent, this.viewContainerRef);\n this.overlayRef.attach(portal);\n\n // The backdrop element only exists once attached — setting this any\n // earlier would silently no-op.\n this.overlayRef.backdropElement?.setAttribute('data-state', 'open');\n }\n\n /**\n * Tears the overlay down once its exit animation finishes (or after\n * `CLOSE_FALLBACK_MS`, whichever comes first) instead of instantly, so\n * `data-[state=closed]:animate-out` actually gets a chance to play.\n */\n private hide() {\n const ref = this.overlayRef;\n if (!ref) return;\n\n ref.backdropElement?.setAttribute('data-state', 'closed');\n\n let finished = false;\n const finish = () => {\n if (finished) return;\n finished = true;\n ref.detach();\n ref.dispose();\n if (this.overlayRef === ref) this.overlayRef = undefined;\n // Restore focus to the trigger.\n this.previouslyFocused?.focus?.();\n };\n\n const panel = ref.overlayElement.querySelector<HTMLElement>('[data-state]');\n if (panel) {\n panel.addEventListener('animationend', finish, { once: true });\n setTimeout(finish, CLOSE_FALLBACK_MS);\n } else {\n finish();\n }\n }\n\n ngOnDestroy() {\n this.hide();\n }\n}\n\n@Component({\n selector: 'tolle-alert-dialog-content',\n standalone: true,\n imports: [CommonModule, A11yModule],\n template: `<div [class]=\"computedClass\" [attr.data-state]=\"isOpen ? 'open' : 'closed'\"\n role=\"alertdialog\"\n aria-modal=\"true\"\n [attr.aria-labelledby]=\"titleCmp?.id || null\"\n [attr.aria-describedby]=\"descriptionCmp?.id || null\"\n cdkTrapFocus cdkTrapFocusAutoCapture><ng-content></ng-content></div>`,\n host: {\n '[class]': '\"contents\"'\n }\n})\nexport class AlertDialogContentComponent {\n @Input() class: string = '';\n @Input() size: AlertDialogSize = 'lg';\n\n /** Projected title/description used to build the dialog's accessible name. */\n @ContentChild(forwardRef(() => AlertDialogTitleComponent)) titleCmp?: AlertDialogTitleComponent;\n @ContentChild(forwardRef(() => AlertDialogDescriptionComponent)) descriptionCmp?: AlertDialogDescriptionComponent;\n\n private alertDialogService = inject(AlertDialogInternalService);\n isOpen = false;\n\n constructor() {\n this.alertDialogService.open$.subscribe(val => {\n this.isOpen = val;\n });\n }\n\n get computedClass() {\n return cn(\n \"fixed left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] z-50 grid w-full gap-4 border border-input bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] rounded-lg\",\n {\n 'max-w-xs': this.size === 'xs',\n 'max-w-sm': this.size === 'sm',\n 'max-w-md': this.size === 'md',\n 'max-w-lg': this.size === 'lg',\n 'max-w-xl': this.size === 'xl',\n 'max-w-2xl': this.size === '2xl',\n 'max-w-full': this.size === 'full',\n 'max-w-fit': this.size === 'fit',\n },\n this.class\n );\n }\n}\n\n@Component({\n selector: 'tolle-alert-dialog-header',\n standalone: true,\n imports: [CommonModule],\n template: `<ng-content></ng-content>`,\n host: { '[class]': 'computedClass' }\n})\nexport class AlertDialogHeaderComponent {\n @Input() class: string = '';\n get computedClass() { return cn(\"flex flex-col space-y-2 text-center sm:text-left\", this.class); }\n}\n\n@Component({\n selector: 'tolle-alert-dialog-footer',\n standalone: true,\n imports: [CommonModule],\n template: `<ng-content></ng-content>`,\n host: { '[class]': 'computedClass' }\n})\nexport class AlertDialogFooterComponent {\n @Input() class: string = '';\n get computedClass() { return cn(\"flex flex-row justify-end space-x-2\", this.class); }\n}\n\n@Component({\n selector: 'tolle-alert-dialog-title',\n styles: [':host { display: block; }'],\n standalone: true,\n imports: [CommonModule],\n template: `<ng-content></ng-content>`,\n host: { '[class]': 'computedClass', '[attr.id]': 'id' }\n})\nexport class AlertDialogTitleComponent {\n @Input() class: string = '';\n /** Stable id referenced by the dialog's `aria-labelledby`. */\n readonly id = `tolle-alert-dialog-title-${Math.random().toString(36).substr(2, 9)}`;\n get computedClass() { return cn(\"text-lg font-semibold text-foreground\", this.class); }\n}\n\n@Component({\n selector: 'tolle-alert-dialog-description',\n styles: [':host { display: block; }'],\n standalone: true,\n imports: [CommonModule],\n template: `<ng-content></ng-content>`,\n host: { '[class]': 'computedClass', '[attr.id]': 'id' }\n})\nexport class AlertDialogDescriptionComponent {\n @Input() class: string = '';\n /** Stable id referenced by the dialog's `aria-describedby`. */\n readonly id = `tolle-alert-dialog-description-${Math.random().toString(36).substr(2, 9)}`;\n get computedClass() { return cn(\"text-sm text-muted-foreground\", this.class); }\n}\n\n@Component({\n selector: 'tolle-alert-dialog-action',\n styles: [':host { display: block; }'],\n standalone: true,\n imports: [CommonModule],\n template: `<ng-content></ng-content>`\n})\nexport class AlertDialogActionComponent {\n private alertDialogService = inject(AlertDialogInternalService, { optional: true });\n\n /** Fires before the dialog closes, so consumers can run the confirmed action. */\n @Output() confirmed = new EventEmitter<MouseEvent>();\n\n @HostListener('click', ['$event'])\n handleClick(event: MouseEvent) {\n this.confirmed.emit(event);\n this.alertDialogService?.setOpen(false);\n }\n}\n\n@Component({\n selector: 'tolle-alert-dialog-cancel',\n styles: [':host { display: block; }'],\n standalone: true,\n imports: [CommonModule],\n template: `<ng-content></ng-content>`\n})\nexport class AlertDialogCancelComponent {\n private alertDialogService = inject(AlertDialogInternalService, { optional: true });\n\n /** Fires before the dialog closes, so consumers can react to a dismissal. */\n @Output() cancelled = new EventEmitter<MouseEvent>();\n\n @HostListener('click', ['$event'])\n handleClick(event: MouseEvent) {\n this.cancelled.emit(event);\n this.alertDialogService?.setOpen(false);\n }\n}\n"
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
"path": "alert-dialog.types.ts",
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bar-list",
|
|
3
|
+
"type": "registry:ui",
|
|
4
|
+
"title": "Bar List",
|
|
5
|
+
"description": "Bar List component.",
|
|
6
|
+
"category": "components",
|
|
7
|
+
"registryDependencies": [],
|
|
8
|
+
"dependencies": [],
|
|
9
|
+
"files": [
|
|
10
|
+
{
|
|
11
|
+
"path": "bar-list.component.ts",
|
|
12
|
+
"target": "ui/bar-list.component.ts",
|
|
13
|
+
"content": "import { Component, Input, ChangeDetectionStrategy, ChangeDetectorRef, OnChanges, inject } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { cn } from './utils/cn';\n\n/** One resolved row: its label, its value, and its share of the list's largest value. */\nexport interface BarListRow {\n label: string;\n value: number;\n percent: number;\n formattedValue: string;\n}\n\n/**\n * A ranked list of horizontal bars — label left, value right, bar width\n * scaled to each row's share of the list's largest value. Unlike a chart\n * series or a category bar's segments, every bar is the same accent: rank is\n * carried by length, not by a categorical palette.\n * @new\n */\n@Component({\n selector: 'tolle-bar-list',\n styles: [':host { display: block; }'],\n standalone: true,\n imports: [CommonModule],\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n <div [class]=\"computedClass\">\n <div\n *ngFor=\"let row of rows; trackBy: trackByIndex\"\n class=\"relative flex items-center overflow-hidden rounded-sm\"\n >\n <div class=\"absolute inset-y-0 left-0 rounded-sm bg-primary/15\" [style.width.%]=\"row.percent\"></div>\n <span class=\"relative z-10 truncate px-2 py-1.5 text-sm text-foreground\">{{ row.label }}</span>\n <span class=\"relative z-10 ml-auto shrink-0 px-2 py-1.5 text-sm font-medium tabular-nums text-foreground\">\n {{ row.formattedValue }}\n </span>\n </div>\n </div>\n `,\n})\nexport class BarListComponent implements OnChanges {\n /**\n * Angular writes a bound `class` input through its styling path, which does\n * not mark an OnPush component dirty — without this hook the component keeps\n * rendering the class it was born with.\n */\n ngOnChanges(): void {\n this.cdr.markForCheck();\n }\n\n /** Identity for `*ngFor`, so a redraw patches nodes instead of replacing them. */\n trackByIndex = (index: number): number => index;\n\n /** Rows to rank, in the order they should be drawn. @default [] */\n @Input() data: Record<string, any>[] = [];\n /** Row key holding each row's label. @default 'label' */\n @Input() labelKey = 'label';\n /** Row key holding each row's numeric value. @default 'value' */\n @Input() valueKey = 'value';\n /** Extra Tailwind classes merged onto the list via `cn()` (last-wins). */\n @Input() class = '';\n\n private readonly cdr = inject(ChangeDetectorRef);\n\n get rows(): BarListRow[] {\n const rows = this.data ?? [];\n const values = rows.map((row) => {\n const num = Number(row?.[this.valueKey]);\n return Number.isFinite(num) ? num : 0;\n });\n const max = values.reduce((m, v) => Math.max(m, v), 0);\n\n return rows.map((row, i) => {\n const raw = row?.[this.labelKey];\n const value = values[i];\n return {\n label: raw == null ? '' : String(raw),\n value,\n percent: max > 0 ? Math.max(0, Math.min(100, (value / max) * 100)) : 0,\n formattedValue: value.toLocaleString('en-US'),\n };\n });\n }\n\n get computedClass() {\n return cn('flex w-full flex-col gap-1', this.class);\n }\n}\n"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"path": "utils/cn.ts",
|
|
17
|
+
"target": "ui/utils/cn.ts",
|
|
18
|
+
"content": "import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "category-bar",
|
|
3
|
+
"type": "registry:ui",
|
|
4
|
+
"title": "Category Bar",
|
|
5
|
+
"description": "Category Bar component.",
|
|
6
|
+
"category": "components",
|
|
7
|
+
"registryDependencies": [],
|
|
8
|
+
"dependencies": [],
|
|
9
|
+
"files": [
|
|
10
|
+
{
|
|
11
|
+
"path": "category-bar.component.ts",
|
|
12
|
+
"target": "ui/category-bar.component.ts",
|
|
13
|
+
"content": "import { Component, Input, ChangeDetectionStrategy, ChangeDetectorRef, OnChanges, inject } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { cn } from './utils/cn';\n\n/** One resolved segment: its share of the bar's width, its paint, and the numbers behind it. */\nexport interface CategoryBarSegment {\n label: string;\n value: number;\n percent: number;\n color: string;\n}\n\n/**\n * A single bar split into proportional, labelled ranges — for showing where\n * one value falls against a scale made of categories (a credit score inside\n * \"Poor / Fair / Good / Excellent\", latency inside \"Fast / OK / Slow\").\n *\n * Segments take the chart palette by position, since a category bar has no\n * stable per-render identity to key colour off the way a chart series does —\n * pass `colors` to override.\n * @new\n */\n@Component({\n selector: 'tolle-category-bar',\n styles: [':host { display: block; }'],\n standalone: true,\n imports: [CommonModule],\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n <div [class]=\"computedClass\">\n <div class=\"relative flex w-full gap-0.5\">\n <div\n *ngFor=\"let segment of segments; let i = index; trackBy: trackByIndex\"\n class=\"h-2 first:rounded-l-full last:rounded-r-full\"\n [style.width.%]=\"segment.percent\"\n [style.background]=\"segment.color\"\n [attr.title]=\"segment.label || null\"\n ></div>\n\n <span\n *ngIf=\"clampedMarker !== null\"\n class=\"absolute top-1/2 h-3 w-3 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-background bg-foreground shadow\"\n [style.left.%]=\"clampedMarker\"\n ></span>\n </div>\n\n <p class=\"sr-only\">{{ summary }}</p>\n </div>\n `,\n})\nexport class CategoryBarComponent implements OnChanges {\n /**\n * Angular writes a bound `class` input through its styling path, which does\n * not mark an OnPush component dirty — without this hook the component keeps\n * rendering the class it was born with.\n */\n ngOnChanges(): void {\n this.cdr.markForCheck();\n }\n\n /** Identity for `*ngFor`, so a redraw patches nodes instead of replacing them. */\n trackByIndex = (index: number): number => index;\n\n /** Width of each segment. Values that are not finite and greater than zero are dropped. @default [] */\n @Input() values: number[] = [];\n /** Name for each segment, used in the accessible summary and as a title tooltip. @default [] */\n @Input() labels: string[] = [];\n /** Paint for each segment, by index. Falls back to the chart palette --chart-1 through --chart-5. @default [] */\n @Input() colors: string[] = [];\n /** Position of the pointer, 0-100 on the same scale the segments sum to. Omit to draw no pointer. @default null */\n @Input() markerValue: number | null = null;\n /** Accessible name, prefixed onto the sr-only summary of segments and marker. @default '' */\n @Input() ariaLabel = '';\n /** Extra Tailwind classes merged onto the bar via `cn()` (last-wins). */\n @Input() class = '';\n\n private readonly cdr = inject(ChangeDetectorRef);\n\n private get total(): number {\n return this.values.reduce((sum, v) => (Number.isFinite(v) && v > 0 ? sum + v : sum), 0);\n }\n\n get segments(): CategoryBarSegment[] {\n const total = this.total;\n if (total <= 0) return [];\n\n return this.values\n .map((value, i) => ({ value, i }))\n .filter(({ value }) => Number.isFinite(value) && value > 0)\n .map(({ value, i }) => ({\n label: this.labels[i] ?? '',\n value,\n percent: (value / total) * 100,\n color: this.colors[i] || 'rgb(var(--chart-' + ((i % 5) + 1) + '))',\n }));\n }\n\n get clampedMarker(): number | null {\n if (this.markerValue == null || !Number.isFinite(this.markerValue)) return null;\n return Math.min(100, Math.max(0, this.markerValue));\n }\n\n get summary(): string {\n const parts = this.segments.map((s) => (s.label ? s.label + ': ' : '') + s.percent.toFixed(0) + '%');\n const marker = this.clampedMarker !== null ? ', current position ' + this.clampedMarker.toFixed(0) + '%' : '';\n const prefix = this.ariaLabel ? this.ariaLabel + '. ' : '';\n return prefix + parts.join(', ') + marker;\n }\n\n get computedClass() {\n return cn('w-full', this.class);\n }\n}\n"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"path": "utils/cn.ts",
|
|
17
|
+
"target": "ui/utils/cn.ts",
|
|
18
|
+
"content": "import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|