Skip to content

Commit da6267e

Browse files
committed
feat: implement word count editor
1 parent e9ab16c commit da6267e

File tree

6 files changed

+179
-13
lines changed

6 files changed

+179
-13
lines changed

apps/docs/src/app/pages/pipes/count.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
1-
import {Component, signal} from '@angular/core';
1+
import {Component} from '@angular/core';
22
import {CountPipe} from '@ngx-transforms';
3-
import {NgIconComponent, provideIcons} from '@ng-icons/core';
4-
import {lucideChevronRight} from '@ng-icons/lucide';
53
import {CodePreview} from "../../reusables/code-preview/code-preview";
6-
import {CommandPreview} from "../../reusables/command-preview/command-preview";
7-
import {NextPrevNavigationComponent} from "../../reusables/next-prev-navigation/next-prev-navigation";
4+
import {NextPrevNavigation} from "../../reusables/next-prev-navigation/next-prev-navigation";
5+
import {MacosWindow} from "../../reusables/macos-window/macos-window";
6+
import {WordEditorCount} from "../../reusables/word-editor-count/word-editor-count";
87

98
@Component({
109
selector: 'app-count-pipe-page',
1110
standalone: true,
12-
imports: [CountPipe, NgIconComponent, CodePreview, CommandPreview, NextPrevNavigationComponent],
13-
providers: [provideIcons({lucideChevronRight})],
11+
imports: [CountPipe, CodePreview, NextPrevNavigation, MacosWindow, WordEditorCount],
1412
template: `
1513
<div class="container mx-auto py-10 px-4 md:px-8 max-w-4xl">
1614
<!-- Breadcrumb -->
1715
<nav class="flex items-center text-sm text-muted-foreground mb-6">
1816
<a href="/docs/pipes" class="hover:text-foreground transition-colors">Pipes</a>
19-
<ng-icon name="lucideChevronRight" class="h-4 w-4 mx-2"></ng-icon>
17+
<span class="h-4 w-4 mx-2">/</span>
2018
<span class="text-foreground font-medium">Count</span>
2119
</nav>
2220
2321
<h1 class="scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl mb-2">Count Pipe</h1>
24-
<p class="text-lg text-muted-foreground mb-8">Returns the length of an array or string.</p>
22+
<p class="text-lg text-muted-foreground mb-8">A simple pipe to count the number of items in an array or characters in a string.</p>
2523
26-
<h2 class="text-2xl font-bold my-8">Installation</h2>
27-
<app-command-preview command="npm install @ngx-transforms/core"></app-command-preview>
24+
<h2 class="text-2xl font-bold my-8">Example</h2>
25+
<app-macos-window title="Mini Word Editor">
26+
<app-word-editor-count />
27+
</app-macos-window>
2828
2929
<h2 class="text-2xl font-bold my-8">Usage</h2>
3030
@@ -82,7 +82,7 @@ export class Count {
8282

8383
code = `
8484
import { Component } from '@angular/core';
85-
import { CountPipe } from '@ngx-transforms/core';
85+
import { CountPipe } from '@ngx-transforms';
8686
8787
@Component({
8888
selector: 'app-example',
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<div class="relative w-full max-w-5xl mx-auto">
2+
<div
3+
class="relative rounded-xl border border-border bg-card/50 shadow-xl backdrop-blur-sm overflow-hidden">
4+
<div class="flex items-center border-b border-border bg-muted/50 px-4 py-2">
5+
<div class="flex space-x-2">
6+
<div class="h-3 w-3 rounded-full bg-red-500/80"></div>
7+
<div class="h-3 w-3 rounded-full bg-yellow-500/80"></div>
8+
<div class="h-3 w-3 rounded-full bg-green-500/80"></div>
9+
</div>
10+
<div class="mx-auto text-xs font-medium text-muted-foreground">{{ title() }}</div>
11+
</div>
12+
<ng-content></ng-content>
13+
</div>
14+
</div>
15+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Component, input } from '@angular/core';
2+
3+
@Component({
4+
selector: 'app-macos-window',
5+
standalone: true,
6+
templateUrl: './macos-window.html',
7+
styles: `
8+
:host {
9+
display: block;
10+
width: 100%;
11+
}
12+
`,
13+
})
14+
export class MacosWindow {
15+
readonly title = input<string>('');
16+
}

apps/docs/src/app/reusables/next-prev-navigation/next-prev-navigation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export interface NavLink {
1515
providers: [provideIcons({ lucideArrowLeft, lucideArrowRight })],
1616
templateUrl: './next-prev-navigation.html',
1717
})
18-
export class NextPrevNavigationComponent {
18+
export class NextPrevNavigation {
1919
readonly next = input<NavLink>();
2020
readonly previous = input<NavLink>();
2121
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<div class="w-full">
2+
<div class="word-editor-toolbar">
3+
<button class="toolbar-button" (click)="execCommand('bold')">
4+
<ng-icon name="lucideBold" class="h-4 w-4"></ng-icon>
5+
</button>
6+
<button class="toolbar-button" (click)="execCommand('italic')">
7+
<ng-icon name="lucideItalic" class="h-4 w-4"></ng-icon>
8+
</button>
9+
<button class="toolbar-button" (click)="execCommand('underline')">
10+
<ng-icon name="lucideUnderline" class="h-4 w-4"></ng-icon>
11+
</button>
12+
</div>
13+
<div
14+
#editor
15+
class="word-editor-content"
16+
contenteditable="true"
17+
(input)="onContentChange($event)"
18+
></div>
19+
<div class="word-editor-footer">
20+
<div class="flex items-center">
21+
<span class="word-count-value">{{ wordCount() }}</span>
22+
<span class="word-count-label">Words</span>
23+
</div>
24+
<div class="divider"></div>
25+
<div class="flex items-center">
26+
<span class="word-count-value">{{ content() | count }}</span>
27+
<span class="word-count-label">Characters</span>
28+
</div>
29+
</div>
30+
</div>
31+
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { Component, computed, signal } from '@angular/core';
2+
import { CountPipe } from '@ngx-transforms';
3+
import { NgIconComponent, provideIcons } from '@ng-icons/core';
4+
import { lucideBold, lucideItalic, lucideUnderline } from '@ng-icons/lucide';
5+
6+
@Component({
7+
selector: 'app-word-editor-count',
8+
standalone: true,
9+
imports: [CountPipe, NgIconComponent],
10+
providers: [provideIcons({ lucideBold, lucideItalic, lucideUnderline })],
11+
templateUrl: './word-editor-count.html',
12+
styles: [`
13+
:host {
14+
display: block;
15+
width: 100%;
16+
}
17+
18+
.word-editor-toolbar {
19+
display: flex;
20+
gap: 0.5rem;
21+
padding: 0.75rem 1rem;
22+
border-bottom: 1px solid var(--border);
23+
background-color: var(--muted/50);
24+
}
25+
26+
.toolbar-button {
27+
padding: 0.25rem;
28+
border: none;
29+
border-radius: var(--radius);
30+
background-color: transparent;
31+
color: var(--muted-foreground);
32+
cursor: pointer;
33+
transition: all 0.2s ease;
34+
}
35+
36+
.toolbar-button:hover {
37+
background-color: var(--accent);
38+
color: var(--accent-foreground);
39+
}
40+
41+
.word-editor-content {
42+
padding: 1rem;
43+
min-height: 8rem;
44+
outline: none;
45+
font-size: 1rem;
46+
line-height: 1.75;
47+
color: var(--foreground);
48+
}
49+
50+
.word-editor-content:empty::before {
51+
content: 'Start typing and see the magic happen...';
52+
color: var(--muted-foreground);
53+
pointer-events: none;
54+
}
55+
56+
.word-editor-footer {
57+
display: flex;
58+
justify-content: flex-end;
59+
align-items: center;
60+
gap: 1.5rem;
61+
padding: 0.75rem 1rem;
62+
border-top: 1px solid var(--border);
63+
background-color: var(--muted/50);
64+
}
65+
66+
.word-count-value {
67+
font-size: 1rem;
68+
font-weight: 600;
69+
color: var(--primary);
70+
margin-right: 0.25rem;
71+
}
72+
73+
.word-count-label {
74+
font-size: 0.75rem;
75+
color: var(--muted-foreground);
76+
}
77+
78+
.divider {
79+
width: 1px;
80+
height: 1.25rem;
81+
background-color: var(--border);
82+
}
83+
`],
84+
})
85+
export class WordEditorCount {
86+
readonly content = signal('');
87+
readonly wordCount = computed(() => {
88+
const content = this.content();
89+
if (!content) {
90+
return 0;
91+
}
92+
return content.split(' ').filter(w => w !== '').length;
93+
});
94+
95+
onContentChange(event: Event) {
96+
const target = event.target as HTMLElement;
97+
this.content.set(target.innerText);
98+
}
99+
100+
execCommand(command: string) {
101+
document.execCommand(command, false, undefined);
102+
}
103+
}
104+

0 commit comments

Comments
 (0)