wally-ui 1.13.1 → 1.14.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 (31) hide show
  1. package/package.json +1 -1
  2. package/playground/showcase/package-lock.json +48 -0
  3. package/playground/showcase/package.json +1 -0
  4. package/playground/showcase/src/app/app.routes.server.ts +4 -0
  5. package/playground/showcase/src/app/components/ai/ai-chat/ai-chat.html +7 -2
  6. package/playground/showcase/src/app/components/ai/ai-chat/ai-chat.ts +12 -1
  7. package/playground/showcase/src/app/components/ai/ai-chat.service.spec.ts +16 -0
  8. package/playground/showcase/src/app/components/ai/ai-chat.service.ts +6 -0
  9. package/playground/showcase/src/app/components/ai/ai-composer/ai-composer.html +14 -7
  10. package/playground/showcase/src/app/components/ai/ai-composer/ai-composer.ts +3 -1
  11. package/playground/showcase/src/app/components/ai/ai-message/ai-message.css +0 -0
  12. package/playground/showcase/src/app/components/ai/ai-message/ai-message.html +165 -0
  13. package/playground/showcase/src/app/components/ai/ai-message/ai-message.spec.ts +23 -0
  14. package/playground/showcase/src/app/components/ai/ai-message/ai-message.ts +51 -0
  15. package/playground/showcase/src/app/components/button/button.html +1 -1
  16. package/playground/showcase/src/app/components/button/button.ts +3 -3
  17. package/playground/showcase/src/app/components/selection-popover/selection-popover.css +0 -0
  18. package/playground/showcase/src/app/components/selection-popover/selection-popover.html +27 -0
  19. package/playground/showcase/src/app/components/selection-popover/selection-popover.spec.ts +23 -0
  20. package/playground/showcase/src/app/components/selection-popover/selection-popover.ts +205 -0
  21. package/playground/showcase/src/app/pages/documentation/chat-sdk/chat-sdk.html +1 -1
  22. package/playground/showcase/src/app/pages/documentation/components/button-docs/button-docs.examples.ts +10 -10
  23. package/playground/showcase/src/app/pages/documentation/components/button-docs/button-docs.html +2 -2
  24. package/playground/showcase/src/app/pages/documentation/components/components.html +27 -0
  25. package/playground/showcase/src/app/pages/documentation/components/components.routes.ts +4 -0
  26. package/playground/showcase/src/app/pages/documentation/components/selection-popover-docs/selection-popover-docs.css +1 -0
  27. package/playground/showcase/src/app/pages/documentation/components/selection-popover-docs/selection-popover-docs.examples.ts +324 -0
  28. package/playground/showcase/src/app/pages/documentation/components/selection-popover-docs/selection-popover-docs.html +506 -0
  29. package/playground/showcase/src/app/pages/documentation/components/selection-popover-docs/selection-popover-docs.ts +96 -0
  30. package/playground/showcase/src/app/pages/home/home.html +2 -2
  31. package/playground/showcase/src/styles.css +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wally-ui",
3
- "version": "1.13.1",
3
+ "version": "1.14.0",
4
4
  "description": "About Where’s Wally? Right here — bringing you ready-to-use Angular components with Wally-UI. Stop searching, start building.",
5
5
  "bin": {
6
6
  "wally": "dist/cli.js"
@@ -29,6 +29,7 @@
29
29
  "@angular/build": "^20.0.5",
30
30
  "@angular/cli": "^20.0.5",
31
31
  "@angular/compiler-cli": "^20.0.0",
32
+ "@tailwindcss/typography": "^0.5.19",
32
33
  "@types/express": "^5.0.1",
33
34
  "@types/jasmine": "~5.1.0",
34
35
  "@types/node": "^20.17.19",
@@ -4100,6 +4101,19 @@
4100
4101
  "tailwindcss": "4.1.13"
4101
4102
  }
4102
4103
  },
4104
+ "node_modules/@tailwindcss/typography": {
4105
+ "version": "0.5.19",
4106
+ "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz",
4107
+ "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==",
4108
+ "dev": true,
4109
+ "license": "MIT",
4110
+ "dependencies": {
4111
+ "postcss-selector-parser": "6.0.10"
4112
+ },
4113
+ "peerDependencies": {
4114
+ "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
4115
+ }
4116
+ },
4103
4117
  "node_modules/@tufjs/canonical-json": {
4104
4118
  "version": "2.0.0",
4105
4119
  "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz",
@@ -5205,6 +5219,19 @@
5205
5219
  "url": "https://github.com/sponsors/fb55"
5206
5220
  }
5207
5221
  },
5222
+ "node_modules/cssesc": {
5223
+ "version": "3.0.0",
5224
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
5225
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
5226
+ "dev": true,
5227
+ "license": "MIT",
5228
+ "bin": {
5229
+ "cssesc": "bin/cssesc"
5230
+ },
5231
+ "engines": {
5232
+ "node": ">=4"
5233
+ }
5234
+ },
5208
5235
  "node_modules/custom-event": {
5209
5236
  "version": "1.0.1",
5210
5237
  "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz",
@@ -8824,6 +8851,20 @@
8824
8851
  "dev": true,
8825
8852
  "license": "MIT"
8826
8853
  },
8854
+ "node_modules/postcss-selector-parser": {
8855
+ "version": "6.0.10",
8856
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
8857
+ "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
8858
+ "dev": true,
8859
+ "license": "MIT",
8860
+ "dependencies": {
8861
+ "cssesc": "^3.0.0",
8862
+ "util-deprecate": "^1.0.2"
8863
+ },
8864
+ "engines": {
8865
+ "node": ">=4"
8866
+ }
8867
+ },
8827
8868
  "node_modules/prismjs": {
8828
8869
  "version": "1.30.0",
8829
8870
  "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
@@ -10208,6 +10249,13 @@
10208
10249
  "node": ">=6"
10209
10250
  }
10210
10251
  },
10252
+ "node_modules/util-deprecate": {
10253
+ "version": "1.0.2",
10254
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
10255
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
10256
+ "dev": true,
10257
+ "license": "MIT"
10258
+ },
10211
10259
  "node_modules/utils-merge": {
10212
10260
  "version": "1.0.1",
10213
10261
  "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
@@ -42,6 +42,7 @@
42
42
  "@angular/build": "^20.0.5",
43
43
  "@angular/cli": "^20.0.5",
44
44
  "@angular/compiler-cli": "^20.0.0",
45
+ "@tailwindcss/typography": "^0.5.19",
45
46
  "@types/express": "^5.0.1",
46
47
  "@types/jasmine": "~5.1.0",
47
48
  "@types/node": "^20.17.19",
@@ -33,6 +33,10 @@ export const serverRoutes: ServerRoute[] = [
33
33
  path: 'documentation/components/dropdown-menu',
34
34
  renderMode: RenderMode.Prerender,
35
35
  },
36
+ {
37
+ path: 'documentation/components/selection-popover',
38
+ renderMode: RenderMode.Prerender,
39
+ },
36
40
  {
37
41
  path: 'documentation/chat-sdk',
38
42
  renderMode: RenderMode.Prerender,
@@ -1,5 +1,10 @@
1
- <div class="w-full border bg-white/50 border-neutral-300 dark:bg-[#1b1b1b] dark:border-neutral-700 shadow-lg rounded-4xl p-1">
1
+
2
+ <div class="mb-2">
3
+ <wally-ai-message (textSelected)="handleTextSelected($event)"></wally-ai-message>
4
+ </div>
5
+ <div
6
+ class="w-full border bg-white/50 border-neutral-300 dark:bg-[#1b1b1b] dark:border-neutral-700 shadow-lg rounded-4xl p-1">
2
7
  <div class="w-full">
3
- <wally-ai-composer></wally-ai-composer>
8
+ <wally-ai-composer [textSelected]="textSelected()"></wally-ai-composer>
4
9
  </div>
5
10
  </div>
@@ -1,17 +1,28 @@
1
- import { Component } from '@angular/core';
1
+ import { Component, signal } from '@angular/core';
2
2
 
3
3
  import { AiPromptInput } from '../ai-prompt-input/ai-prompt-input';
4
4
  import { AiComposer } from '../ai-composer/ai-composer';
5
+ import { AiChatService } from '../ai-chat.service';
6
+ import { AiMessage } from '../ai-message/ai-message';
5
7
 
6
8
  @Component({
7
9
  selector: 'wally-ai-chat',
8
10
  imports: [
9
11
  AiComposer,
10
12
  AiPromptInput,
13
+ AiMessage
14
+ ],
15
+ providers: [
16
+ AiChatService
11
17
  ],
12
18
  templateUrl: './ai-chat.html',
13
19
  styleUrl: './ai-chat.css'
14
20
  })
15
21
  export class AiChat {
22
+ textSelected = signal<string>('');
16
23
 
24
+ handleTextSelected(text: string): void {
25
+ console.log('📝 Texto selecionado:', text);
26
+ this.textSelected.set(text);
27
+ }
17
28
  }
@@ -0,0 +1,16 @@
1
+ import { TestBed } from '@angular/core/testing';
2
+
3
+ import { AiChatService } from './ai-chat.service';
4
+
5
+ describe('AiChatService', () => {
6
+ let service: AiChatService;
7
+
8
+ beforeEach(() => {
9
+ TestBed.configureTestingModule({});
10
+ service = TestBed.inject(AiChatService);
11
+ });
12
+
13
+ it('should be created', () => {
14
+ expect(service).toBeTruthy();
15
+ });
16
+ });
@@ -0,0 +1,6 @@
1
+ import { Injectable } from '@angular/core';
2
+
3
+ @Injectable()
4
+ export class AiChatService {
5
+
6
+ }
@@ -1,25 +1,31 @@
1
1
  <div class="w-full flex flex-col" role="region" aria-label="AI message composer">
2
2
  <!-- Selected Text Context -->
3
+ @if (textSelected()) {
3
4
  <div class="p-0.5">
4
5
  <div id="selected-context"
5
- class="w-full flex items-center gap-1 justify-between border bg-neutral-100 border-neutral-300 dark:bg-[#121212] dark:border-neutral-700 rounded-t-3xl rounded-b-lg p-3"
6
+ class="w-full max-h-36 overflow-auto flex items-start gap-4 justify-between border bg-neutral-100 border-neutral-300 dark:bg-[#121212] dark:border-neutral-700 rounded-t-3xl rounded-b-lg px-3 py-4"
6
7
  role="status" aria-live="polite">
7
- <div class="w-full flex gap-4 items-center">
8
+ <!-- Ícone Esquerda -->
9
+ <div class="flex items-center shrink-0">
8
10
  <svg viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg"
9
11
  class="size-5 text-[#0a0a0a] dark:text-white" aria-hidden="true">
10
12
  <path
11
13
  d="M12.5293 6.5293C12.7566 6.30203 13.1081 6.27383 13.3662 6.44434L13.4707 6.5293L17.4707 10.5293C17.7304 10.789 17.7304 11.211 17.4707 11.4707L13.4707 15.4707C13.211 15.7304 12.789 15.7304 12.5293 15.4707C12.2696 15.211 12.2696 14.789 12.5293 14.5293L15.3936 11.665H6C3.97588 11.665 2.33496 10.0241 2.33496 8V4.5C2.33496 4.13273 2.63273 3.83496 3 3.83496C3.36727 3.83496 3.66504 4.13273 3.66504 4.5V8C3.66504 9.28958 4.71042 10.335 6 10.335H15.3936L12.5293 7.4707L12.4443 7.36621C12.2738 7.10808 12.302 6.75657 12.5293 6.5293Z">
12
14
  </path>
13
15
  </svg>
16
+ </div>
14
17
 
15
- <p class="text-sm text-[#0a0a0a] dark:text-white" id="context-label">
16
- "Ask about"
18
+ <!-- Texto Central -->
19
+ <div class="flex-1 flex items-center">
20
+ <p class="text-sm text-[#0a0a0a] dark:text-white line-clamp-2" id="context-label">
21
+ "{{ textSelected() }}"
17
22
  </p>
18
23
  </div>
19
24
 
20
- <div>
25
+ <!-- Botão Direita -->
26
+ <div class="flex items-center h-5">
21
27
  <wally-button [variant]="'ghost'" [rounded]="true" [ariaLabel]="'Clear selected context'">
22
- <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
28
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2"
23
29
  stroke="currentColor" class="size-5" aria-hidden="true">
24
30
  <path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
25
31
  </svg>
@@ -27,13 +33,14 @@
27
33
  </div>
28
34
  </div>
29
35
  </div>
36
+ }
30
37
 
31
38
  <div class="w-full px-4 pt-4">
32
39
  <wally-ai-prompt-input></wally-ai-prompt-input>
33
40
  </div>
34
41
 
35
42
  <!-- Action Buttons -->
36
- <div class="w-full flex justify-between px-2 pb-2" role="toolbar" aria-label="Composer actions">
43
+ <div class="w-full flex items-center justify-between p-1" role="toolbar" aria-label="Composer actions">
37
44
  <div class="w-full flex items-center gap-1">
38
45
  <div class="size-10">
39
46
  <wally-dropdown-menu>
@@ -1,4 +1,4 @@
1
- import { Component } from '@angular/core';
1
+ import { Component, input } from '@angular/core';
2
2
 
3
3
  import { DropdownMenuSubTrigger } from '../../dropdown-menu/dropdown-menu-sub-trigger/dropdown-menu-sub-trigger';
4
4
  import { DropdownMenuSubContent } from '../../dropdown-menu/dropdown-menu-sub-content/dropdown-menu-sub-content';
@@ -35,6 +35,8 @@ import { Button } from '../../button/button';
35
35
  styleUrl: './ai-composer.css'
36
36
  })
37
37
  export class AiComposer {
38
+ textSelected = input<string>('');
39
+
38
40
  onItemClick(): void {
39
41
  console.log('Item clicked');
40
42
  }
@@ -0,0 +1,165 @@
1
+ <wally-selection-popover (textSelected)="onAskAbout($event)">
2
+ <!-- Ações customizadas do popover -->
3
+ <div popoverActions class="flex gap-2">
4
+ <wally-button variant="ghost" (buttonClick)="askChat()">
5
+ <div class="flex items-center gap-2">
6
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg"
7
+ class="icon">
8
+ <path
9
+ d="M6.24992 11.0417C6.65578 11.0417 7.04227 10.9587 7.39339 10.809C7.05099 11.7065 6.35374 12.9827 5.29864 13.1333C4.84303 13.1985 4.52644 13.6206 4.59153 14.0762C4.65662 14.5318 5.07873 14.8484 5.53434 14.7832C7.66169 14.4794 8.93792 12.0592 9.30742 10.1591C9.50975 9.11833 9.48792 7.98412 9.04084 7.03938C8.53525 5.97097 7.43538 5.20335 6.23748 5.20834C4.63238 5.21504 3.33325 6.5183 3.33325 8.12498C3.33325 9.73583 4.63909 11.0417 6.24992 11.0417Z">
10
+ </path>
11
+ <path
12
+ d="M13.4832 11.0417C13.889 11.0417 14.2755 10.9587 14.6267 10.809C14.2843 11.7065 13.587 12.9827 12.5319 13.1333C12.0763 13.1985 11.7597 13.6206 11.8248 14.0762C11.8899 14.5318 12.312 14.8484 12.7676 14.7832C14.8949 14.4794 16.1712 12.0592 16.5407 10.1591C16.743 9.11833 16.7212 7.98412 16.2741 7.03938C15.7685 5.97097 14.6687 5.20335 13.4708 5.20834C11.8656 5.21504 10.5665 6.5183 10.5665 8.12498C10.5665 9.73583 11.8724 11.0417 13.4832 11.0417Z">
13
+ </path>
14
+ </svg>
15
+
16
+ <span class="text-base">
17
+ Ask chat
18
+ </span>
19
+ </div>
20
+ </wally-button>
21
+ </div>
22
+
23
+ <article class="prose dark:prose-invert lg:prose-lg prose-neutral w-full break-words">
24
+ <h1>Garlic bread with cheese: What the science tells us</h1>
25
+ <h2>Garlic bread with cheese: What the science tells us</h2>
26
+ <p>
27
+ For years parents have espoused the health benefits of eating garlic bread with cheese to their children,
28
+ with
29
+ the
30
+ food earning such an iconic status in our culture that kids will often dress up as warm, cheesy loaf for
31
+ Halloween.
32
+ </p>
33
+ <p>
34
+ For years parents have espoused the health benefits of eating garlic bread with cheese to their children,
35
+ with
36
+ the
37
+ food earning such an iconic status in our culture that kids will often dress up as warm, cheesy loaf for
38
+ Halloween.
39
+ </p>
40
+ <p>
41
+ But a recent study shows that the celebrated appetizer may be linked to a series of rabies cases springing
42
+ up
43
+ around
44
+ the country.
45
+ </p>
46
+
47
+ <div class="w-full flex gap-1">
48
+ <div class="size-10">
49
+ <wally-tooltip text="Copy to clipboard" [position]="'bottom'">
50
+ <wally-button variant="ghost">
51
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2"
52
+ stroke="currentColor" class="size-5">
53
+ <path stroke-linecap="round" stroke-linejoin="round"
54
+ d="M15.666 3.888A2.25 2.25 0 0 0 13.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 0 1-.75.75H9a.75.75 0 0 1-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 0 1-2.25 2.25H6.75A2.25 2.25 0 0 1 4.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 0 1 1.927-.184" />
55
+ </svg>
56
+ </wally-button>
57
+ </wally-tooltip>
58
+ </div>
59
+
60
+ <div class="size-10">
61
+ <wally-tooltip text="Satisfactory answer" [position]="'bottom'">
62
+ <wally-button variant="ghost">
63
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2"
64
+ stroke="currentColor" class="size-5">
65
+ <path stroke-linecap="round" stroke-linejoin="round"
66
+ d="M6.633 10.25c.806 0 1.533-.446 2.031-1.08a9.041 9.041 0 0 1 2.861-2.4c.723-.384 1.35-.956 1.653-1.715a4.498 4.498 0 0 0 .322-1.672V2.75a.75.75 0 0 1 .75-.75 2.25 2.25 0 0 1 2.25 2.25c0 1.152-.26 2.243-.723 3.218-.266.558.107 1.282.725 1.282m0 0h3.126c1.026 0 1.945.694 2.054 1.715.045.422.068.85.068 1.285a11.95 11.95 0 0 1-2.649 7.521c-.388.482-.987.729-1.605.729H13.48c-.483 0-.964-.078-1.423-.23l-3.114-1.04a4.501 4.501 0 0 0-1.423-.23H5.904m10.598-9.75H14.25M5.904 18.5c.083.205.173.405.27.602.197.4-.078.898-.523.898h-.908c-.889 0-1.713-.518-1.972-1.368a12 12 0 0 1-.521-3.507c0-1.553.295-3.036.831-4.398C3.387 9.953 4.167 9.5 5 9.5h1.053c.472 0 .745.556.5.96a8.958 8.958 0 0 0-1.302 4.665c0 1.194.232 2.333.654 3.375Z" />
67
+ </svg>
68
+
69
+ </wally-button>
70
+ </wally-tooltip>
71
+ </div>
72
+
73
+ <div class="size-10">
74
+ <wally-tooltip text="Unsatisfactory answer" [position]="'bottom'">
75
+ <wally-button variant="ghost">
76
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2"
77
+ stroke="currentColor" class="size-5">
78
+ <path stroke-linecap="round" stroke-linejoin="round"
79
+ d="M7.498 15.25H4.372c-1.026 0-1.945-.694-2.054-1.715a12.137 12.137 0 0 1-.068-1.285c0-2.848.992-5.464 2.649-7.521C5.287 4.247 5.886 4 6.504 4h4.016a4.5 4.5 0 0 1 1.423.23l3.114 1.04a4.5 4.5 0 0 0 1.423.23h1.294M7.498 15.25c.618 0 .991.724.725 1.282A7.471 7.471 0 0 0 7.5 19.75 2.25 2.25 0 0 0 9.75 22a.75.75 0 0 0 .75-.75v-.633c0-.573.11-1.14.322-1.672.304-.76.93-1.33 1.653-1.715a9.04 9.04 0 0 0 2.86-2.4c.498-.634 1.226-1.08 2.032-1.08h.384m-10.253 1.5H9.7m8.075-9.75c.01.05.027.1.05.148.593 1.2.925 2.55.925 3.977 0 1.487-.36 2.89-.999 4.125m.023-8.25c-.076-.365.183-.75.575-.75h.908c.889 0 1.713.518 1.972 1.368.339 1.11.521 2.287.521 3.507 0 1.553-.295 3.036-.831 4.398-.306.774-1.086 1.227-1.918 1.227h-1.053c-.472 0-.745-.556-.5-.96a8.95 8.95 0 0 0 .303-.54" />
80
+ </svg>
81
+ </wally-button>
82
+ </wally-tooltip>
83
+ </div>
84
+
85
+ <div class="size-10">
86
+ <wally-tooltip text="Share" [position]="'bottom'">
87
+ <wally-button variant="ghost">
88
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2"
89
+ stroke="currentColor" class="size-5">
90
+ <path stroke-linecap="round" stroke-linejoin="round"
91
+ d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" />
92
+ </svg>
93
+ </wally-button>
94
+ </wally-tooltip>
95
+ </div>
96
+
97
+ <div class="size-10">
98
+ <wally-tooltip text="Regenerate response" [position]="'bottom'">
99
+ <wally-button variant="ghost">
100
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2"
101
+ stroke="currentColor" class="size-5">
102
+ <path stroke-linecap="round" stroke-linejoin="round"
103
+ d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
104
+ </svg>
105
+ </wally-button>
106
+ </wally-tooltip>
107
+ </div>
108
+
109
+ <div class="size-10">
110
+ <wally-dropdown-menu>
111
+ <wally-dropdown-menu-trigger>
112
+ <wally-tooltip text="More options" [position]="'bottom'">
113
+ <wally-button variant="ghost">
114
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2"
115
+ stroke="currentColor" class="size-5">
116
+ <path stroke-linecap="round" stroke-linejoin="round"
117
+ d="M6.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM12.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM18.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z" />
118
+ </svg>
119
+ </wally-button>
120
+ </wally-tooltip>
121
+ </wally-dropdown-menu-trigger>
122
+
123
+ <wally-dropdown-menu-content>
124
+ <wally-dropdown-menu-group>
125
+ <wally-dropdown-menu-item>
126
+ <div class="flex items-center gap-3">
127
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
128
+ stroke-width="2" stroke="currentColor" class="size-5">
129
+ <path stroke-linecap="round" stroke-linejoin="round"
130
+ d="M19.114 5.636a9 9 0 0 1 0 12.728M16.463 8.288a5.25 5.25 0 0 1 0 7.424M6.75 8.25l4.72-4.72a.75.75 0 0 1 1.28.53v15.88a.75.75 0 0 1-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.009 9.009 0 0 1 2.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75Z" />
131
+ </svg>
132
+
133
+ <span>
134
+ Read aloud
135
+ </span>
136
+ </div>
137
+ </wally-dropdown-menu-item>
138
+ </wally-dropdown-menu-group>
139
+ </wally-dropdown-menu-content>
140
+ </wally-dropdown-menu>
141
+ </div>
142
+
143
+ <div>
144
+ <wally-button variant="ghost" [rounded]="true">
145
+ <div class="flex items-center gap-2">
146
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" class="size-5">
147
+ <path fill="#FFC107"
148
+ d="M43.611,20.083H42V20H24v8h11.303c-1.649,4.657-6.08,8-11.303,8c-6.627,0-12-5.373-12-12c0-6.627,5.373-12,12-12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C12.955,4,4,12.955,4,24c0,11.045,8.955,20,20,20c11.045,0,20-8.955,20-20C44,22.659,43.862,21.35,43.611,20.083z" />
149
+ <path fill="#FF3D00"
150
+ d="M6.306,14.691l6.571,4.819C14.655,15.108,18.961,12,24,12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C16.318,4,9.656,8.337,6.306,14.691z" />
151
+ <path fill="#4CAF50"
152
+ d="M24,44c5.166,0,9.86-1.977,13.409-5.192l-6.19-5.238C29.211,35.091,26.715,36,24,36c-5.202,0-9.619-3.317-11.283-7.946l-6.522,5.025C9.505,39.556,16.227,44,24,44z" />
153
+ <path fill="#1976D2"
154
+ d="M43.611,20.083H42V20H24v8h11.303c-0.792,2.237-2.231,4.166-4.087,5.571c0.001-0.001,0.002-0.001,0.003-0.002l6.19,5.238C36.971,39.205,44,34,44,24C44,22.659,43.862,21.35,43.611,20.083z" />
155
+ </svg>
156
+
157
+ <span>
158
+ Sources
159
+ </span>
160
+ </div>
161
+ </wally-button>
162
+ </div>
163
+ </div>
164
+ </article>
165
+ </wally-selection-popover>
@@ -0,0 +1,23 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { AiMessage } from './ai-message';
4
+
5
+ describe('AiMessage', () => {
6
+ let component: AiMessage;
7
+ let fixture: ComponentFixture<AiMessage>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ imports: [AiMessage]
12
+ })
13
+ .compileComponents();
14
+
15
+ fixture = TestBed.createComponent(AiMessage);
16
+ component = fixture.componentInstance;
17
+ fixture.detectChanges();
18
+ });
19
+
20
+ it('should create', () => {
21
+ expect(component).toBeTruthy();
22
+ });
23
+ });
@@ -0,0 +1,51 @@
1
+ import { Component, input, InputSignal, output, OutputEmitterRef } from '@angular/core';
2
+
3
+ import { Button } from '../../button/button';
4
+ import { Tooltip } from '../../tooltip/tooltip';
5
+ import { DropdownMenu } from '../../dropdown-menu/dropdown-menu';
6
+ import { DropdownMenuContent } from '../../dropdown-menu/dropdown-menu-content/dropdown-menu-content';
7
+ import { DropdownMenuTrigger } from '../../dropdown-menu/dropdown-menu-trigger/dropdown-menu-trigger';
8
+ import { DropdownMenuItem } from '../../dropdown-menu/dropdown-menu-item/dropdown-menu-item';
9
+ import { DropdownMenuGroup } from '../../dropdown-menu/dropdown-menu-group/dropdown-menu-group';
10
+ import { SelectionPopover } from '../../selection-popover/selection-popover';
11
+
12
+ type role = 'user' | 'assistant';
13
+
14
+ @Component({
15
+ selector: 'wally-ai-message',
16
+ imports: [
17
+ Button,
18
+ Tooltip,
19
+ DropdownMenu,
20
+ DropdownMenuTrigger,
21
+ DropdownMenuContent,
22
+ DropdownMenuGroup,
23
+ DropdownMenuItem,
24
+ SelectionPopover
25
+ ],
26
+ templateUrl: './ai-message.html',
27
+ styleUrl: './ai-message.css'
28
+ })
29
+ export class AiMessage {
30
+ inputRole: InputSignal<role> = input<role>('user');
31
+
32
+ textSelected: OutputEmitterRef<string> = output<string>();
33
+
34
+ onAskAbout(text: string): void {
35
+ // console.log('📝 Texto selecionado:', text);
36
+ // console.log('📏 Tamanho do texto:', text.length);
37
+ this.textSelected.emit(text);
38
+ // Aqui você pode emitir um evento para o componente pai (ai-chat)
39
+ // ou chamar um serviço para adicionar a pergunta ao chat
40
+ }
41
+
42
+ askChat(): void {
43
+ // console.log('🤖 Botão "Ask chat" clicado');
44
+ // Este método agora é redundante - o texto é emitido via textSelected
45
+ }
46
+
47
+ copyText(): void {
48
+ // console.log('Copiando texto selecionado');
49
+ // Lógica para copiar o texto para a área de transferência
50
+ }
51
+ }
@@ -1,6 +1,6 @@
1
1
  <button [type]="type()" [disabled]="disabled() || loading()" [attr.aria-label]="ariaLabel() || null"
2
2
  [attr.aria-describedby]="ariaDescribedBy() || null" [attr.aria-pressed]="ariaPressed()" [attr.aria-busy]="loading()"
3
- (click)="handleClick()" [class]="'group relative w-full flex items-center justify-center gap-2 text-sm font-medium text-white active:scale-95 disabled:pointer-events-none p-2.5 disabled:active:scale-100 transition-all duration-300 ease-in-out antialiased cursor-pointer ' + variantClasses()" [ngClass]="{
3
+ (click)="handleClick($event)" [class]="'group relative w-full flex items-center justify-center gap-2 text-sm font-medium text-white active:scale-95 disabled:pointer-events-none p-2.5 disabled:active:scale-100 transition-all duration-300 ease-in-out antialiased cursor-pointer ' + variantClasses()" [ngClass]="{
4
4
  'rounded-md': !rounded(),
5
5
  'rounded-full': rounded()
6
6
  }">
@@ -28,7 +28,7 @@ export class Button {
28
28
  ariaDescribedBy: InputSignal<string> = input<string>('');
29
29
  ariaPressed: InputSignal<boolean | undefined> = input<boolean | undefined>(undefined);
30
30
 
31
- click: OutputEmitterRef<void> = output<void>();
31
+ buttonClick: OutputEmitterRef<void> = output<void>();
32
32
 
33
33
  // Computed classes based on variant
34
34
  variantClasses: Signal<string> = computed(() => {
@@ -44,7 +44,7 @@ export class Button {
44
44
  return variantMap[this.variant()] || variantMap.primary;
45
45
  });
46
46
 
47
- handleClick(): void {
47
+ handleClick(event: MouseEvent): void {
48
48
  if (this.variant() === 'link' && this.href()) {
49
49
  if (this.href().startsWith('http://') || this.href().startsWith('https://')) {
50
50
  window.open(this.href(), '_blank');
@@ -53,6 +53,6 @@ export class Button {
53
53
  }
54
54
  }
55
55
 
56
- this.click.emit();
56
+ this.buttonClick.emit();
57
57
  }
58
58
  }
@@ -0,0 +1,27 @@
1
+ <div class="relative">
2
+ <!-- Original content (where user will select text) -->
3
+ <ng-content></ng-content>
4
+
5
+ <!-- Floating popover -->
6
+ @if (isVisible()) {
7
+ <div #popover
8
+ class="fixed z-50 bg-white dark:bg-neutral-900 shadow-lg rounded-xl border border-neutral-300 dark:border-neutral-700 p-1 transition-opacity duration-150"
9
+ [class.opacity-0]="!isPositioned()"
10
+ [class.opacity-100]="isPositioned()"
11
+ [style.top.px]="adjustedPosition().top" [style.left.px]="adjustedPosition().left" role="dialog"
12
+ aria-label="Selection actions"
13
+ (click)="onPopoverClick()">
14
+
15
+ <!-- Customizable content via content projection (always rendered) -->
16
+ <div #customActionsSlot [class.hidden]="!hasCustomActionsSignal()">
17
+ <ng-content select="[popoverActions]"></ng-content>
18
+ </div>
19
+
20
+ <!-- Fallback: default button (hidden if custom actions exist) -->
21
+ <button [class.hidden]="hasCustomActionsSignal()"
22
+ class="px-3 py-2 text-[#0a0a0a] text-sm font-mono hover:bg-neutral-100 dark:text-white dark:hover:bg-neutral-800 rounded transition-colors cursor-pointer">
23
+ Default Action
24
+ </button>
25
+ </div>
26
+ }
27
+ </div>
@@ -0,0 +1,23 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { SelectionPopover } from './selection-popover';
4
+
5
+ describe('SelectionPopover', () => {
6
+ let component: SelectionPopover;
7
+ let fixture: ComponentFixture<SelectionPopover>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ imports: [SelectionPopover]
12
+ })
13
+ .compileComponents();
14
+
15
+ fixture = TestBed.createComponent(SelectionPopover);
16
+ component = fixture.componentInstance;
17
+ fixture.detectChanges();
18
+ });
19
+
20
+ it('should create', () => {
21
+ expect(component).toBeTruthy();
22
+ });
23
+ });