@nice2dev/social 0.1.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ All notable changes to **@nice2dev/social** will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] — 2025-06-01
9
+
10
+ ### Added
11
+ - `NiceComments` — threaded comments with replies, reactions, and moderation
12
+ - `NiceRatings` — star/emoji/score ratings with aggregation
13
+ - `NiceSocialPanel` — likes, shares, and bookmarks panel
14
+ - `NiceTagCloud` — weighted tag cloud with click handling
15
+ - `NiceUserList` — user list with avatars, status, and filtering
16
+ - `NiceWiki` — wiki module with page tree, content viewer/editor
17
+ - `NiceActivityFeed` — chronological activity stream
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 NiceToDev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # @nice2dev/social
2
+
3
+ Social & community components for React applications — comments, ratings, activity feeds, tag clouds, user lists, and a wiki module.
4
+
5
+ [![npm](https://img.shields.io/npm/v/@nice2dev/social)](https://www.npmjs.com/package/@nice2dev/social)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
8
+
9
+ ---
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install @nice2dev/social
15
+ ```
16
+
17
+ **Peer dependencies:** `react >= 17.0.0`, `react-dom >= 17.0.0`
18
+
19
+ ## Components
20
+
21
+ | Component | Description |
22
+ |---|---|
23
+ | `NiceComments` | Threaded comments with replies, reactions, and moderation |
24
+ | `NiceRatings` | Star/emoji/score ratings with aggregation display |
25
+ | `NiceSocialPanel` | Likes, shares, and bookmarks action panel |
26
+ | `NiceTagCloud` | Weighted tag cloud with click handling |
27
+ | `NiceUserList` | User list with avatars, online status, and filtering |
28
+ | `NiceWiki` | Wiki module with page tree sidebar and content viewer/editor |
29
+ | `NiceActivityFeed` | Chronological activity feed with grouping |
30
+
31
+ ## Usage
32
+
33
+ ```tsx
34
+ import { NiceComments, NiceRatings, NiceActivityFeed } from '@nice2dev/social';
35
+
36
+ function CommunityPage({ entityId }) {
37
+ return (
38
+ <>
39
+ <NiceRatings entityId={entityId} mode="stars" />
40
+ <NiceComments threadId={entityId} allowReplies />
41
+ <NiceActivityFeed entityId={entityId} />
42
+ </>
43
+ );
44
+ }
45
+ ```
46
+
47
+ ## Types
48
+
49
+ Key TypeScript types exported: `CommentEntry`, `RatingMode`, `RatingAggregation`, `SocialStats`, `TagEntry`, `UserEntry`, `WikiPage`, `ActivityEvent`.
50
+
51
+ ## License
52
+
53
+ MIT © NiceToDev
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),l=require("react"),O=l.forwardRef(function(T,E){const{comments:m,currentUserId:n,onSubmit:o,onEdit:h,onDelete:p,onReact:r,readOnly:y=!1,maxDepth:S=4,reactions:C=["👍","❤️","😂","😮","😢"],placeholder:j="Write a comment…",className:b,style:k}=T,[x,s]=l.useState(""),[u,f]=l.useState(null),[t,i]=l.useState(""),[c,a]=l.useState(null),[g,M]=l.useState(""),L=new Map;for(const d of m){const I=d.parentId??"__root__";L.has(I)||L.set(I,[]),L.get(I).push(d)}const R=l.useCallback(()=>{!x.trim()||!o||(o(x.trim()),s(""))},[x,o]),P=l.useCallback(()=>{!t.trim()||!o||!u||(o(t.trim(),u),i(""),f(null))},[t,u,o]),w=l.useCallback(()=>{!c||!g.trim()||!h||(h(c,g.trim()),a(null),M(""))},[c,g,h]),$=(d,I)=>{const F=n&&d.authorId===n,_=L.get(d.id)??[];return e.jsxs("div",{className:"nice-comments__item",style:{marginLeft:I*24},children:[e.jsxs("div",{className:"nice-comments__header",children:[d.authorAvatarUrl&&e.jsx("img",{src:d.authorAvatarUrl,alt:"",className:"nice-comments__avatar"}),e.jsx("strong",{className:"nice-comments__author",children:d.authorName}),e.jsx("time",{className:"nice-comments__time",children:d.createdAt}),d.isEdited&&e.jsx("span",{className:"nice-comments__edited",children:"(edited)"})]}),c===d.id?e.jsxs("div",{className:"nice-comments__edit-form",children:[e.jsx("textarea",{value:g,onChange:N=>M(N.target.value)}),e.jsx("button",{onClick:w,children:"Save"}),e.jsx("button",{onClick:()=>a(null),children:"Cancel"})]}):e.jsx("p",{className:"nice-comments__content",children:d.isDeleted?e.jsx("em",{children:"Comment deleted"}):d.content}),!d.isDeleted&&d.reactions&&e.jsx("div",{className:"nice-comments__reactions",children:Object.entries(d.reactions).map(([N,v])=>e.jsxs("button",{className:"nice-comments__reaction",onClick:()=>r==null?void 0:r(d.id,N),children:[N," ",v]},N))}),!d.isDeleted&&!y&&e.jsxs("div",{className:"nice-comments__actions",children:[r&&C.map(N=>e.jsx("button",{onClick:()=>r(d.id,N),title:N,children:N},N)),I<S&&e.jsx("button",{onClick:()=>{f(d.id),i("")},children:"Reply"}),F&&h&&e.jsx("button",{onClick:()=>{a(d.id),M(d.content)},children:"Edit"}),F&&p&&e.jsx("button",{onClick:()=>p(d.id),children:"Delete"})]}),u===d.id&&e.jsxs("div",{className:"nice-comments__reply-form",children:[e.jsx("textarea",{value:t,onChange:N=>i(N.target.value),placeholder:"Write a reply…"}),e.jsx("button",{onClick:P,disabled:!t.trim(),children:"Send"}),e.jsx("button",{onClick:()=>f(null),children:"Cancel"})]}),_.map(N=>$(N,I+1))]},d.id)},D=L.get("__root__")??[];return e.jsxs("div",{ref:E,className:`nice-comments ${b??""}`,style:k,children:[!y&&e.jsxs("div",{className:"nice-comments__new",children:[e.jsx("textarea",{value:x,onChange:d=>s(d.target.value),placeholder:j}),e.jsx("button",{onClick:R,disabled:!x.trim(),children:"Post"})]}),D.length===0?e.jsx("p",{className:"nice-comments__empty",children:"No comments yet."}):D.map(d=>$(d,0))]})}),z="☆",B="★",K="♡",H="♥",q=l.forwardRef(function(T,E){const{value:m,max:n=5,mode:o="stars",aggregation:h,onChange:p,readOnly:r=!1,size:y="md",showValue:S=!1,label:C,emojiSet:j=["😢","😕","😐","🙂","😍"],className:b,style:k}=T,[x,s]=l.useState(null),u=x!==null?x+1:m??0,f=l.useCallback(c=>{r||!p||p(c+1)},[r,p]),t=()=>{const c=[];for(let a=0;a<n;a++){const g=a<u;let M;o==="emoji"?M=j[a]??j[j.length-1]:o==="hearts"?M=g?H:K:o==="thumbs"?M=g?"👍":"👎":M=g?B:z,c.push(e.jsx("span",{className:`nice-ratings__symbol ${g?"nice-ratings__symbol--active":""}`,onClick:()=>f(a),onMouseEnter:()=>!r&&s(a),onMouseLeave:()=>!r&&s(null),role:r?void 0:"button",tabIndex:r?void 0:0,onKeyDown:L=>{(L.key==="Enter"||L.key===" ")&&f(a)},children:M},a))}return c},i=()=>e.jsxs("div",{className:"nice-ratings__numeric",children:[e.jsx("input",{type:"number",min:0,max:n,step:.5,value:m??0,onChange:c=>p==null?void 0:p(parseFloat(c.target.value)||0),readOnly:r,className:"nice-ratings__numeric-input"}),e.jsxs("span",{className:"nice-ratings__numeric-max",children:["/ ",n]})]});return e.jsxs("div",{ref:E,className:`nice-ratings nice-ratings--${y} nice-ratings--${o} ${r?"nice-ratings--readonly":""} ${b??""}`,style:k,children:[C&&e.jsx("span",{className:"nice-ratings__label",children:C}),e.jsxs("div",{className:"nice-ratings__body",children:[o==="numeric"?i():t(),S&&o!=="numeric"&&e.jsxs("span",{className:"nice-ratings__value",children:[u," / ",n]})]}),h&&e.jsxs("div",{className:"nice-ratings__aggregation",children:[e.jsx("span",{className:"nice-ratings__avg",children:h.average.toFixed(1)}),e.jsxs("span",{className:"nice-ratings__count",children:["(",h.count," ratings)"]}),h.distribution&&e.jsx("div",{className:"nice-ratings__distribution",children:Object.entries(h.distribution).sort(([c],[a])=>+a-+c).map(([c,a])=>e.jsxs("div",{className:"nice-ratings__distribution-row",children:[e.jsx("span",{children:c}),e.jsx("div",{className:"nice-ratings__bar",children:e.jsx("div",{className:"nice-ratings__bar-fill",style:{width:`${h.count?a/h.count*100:0}%`}})}),e.jsx("span",{children:a})]},c))})]})]})}),V=l.forwardRef(function(T,E){const{stats:m,userState:n,onLike:o,onShare:h,onBookmark:p,direction:r="horizontal",size:y="md",showCounts:S=!0,showLabels:C=!1,className:j,style:b}=T,k=l.useCallback(s=>s>=1e6?(s/1e6).toFixed(1)+"M":s>=1e3?(s/1e3).toFixed(1)+"K":s.toString(),[]),x=[{key:"like",icon:"♡",activeIcon:"♥",label:"Like",count:m.likes,isActive:(n==null?void 0:n.liked)??!1,onClick:o},{key:"share",icon:"↗",activeIcon:"↗",label:"Share",count:m.shares,isActive:(n==null?void 0:n.shared)??!1,onClick:h},{key:"bookmark",icon:"☆",activeIcon:"★",label:"Bookmark",count:m.bookmarks,isActive:(n==null?void 0:n.bookmarked)??!1,onClick:p}];return e.jsx("div",{ref:E,className:`nice-social-panel nice-social-panel--${r} nice-social-panel--${y} ${j??""}`,style:b,children:x.map(s=>e.jsxs("button",{className:`nice-social-panel__btn nice-social-panel__btn--${s.key} ${s.isActive?"nice-social-panel__btn--active":""}`,onClick:s.onClick,title:s.label,children:[e.jsx("span",{className:"nice-social-panel__icon",children:s.isActive?s.activeIcon:s.icon}),C&&e.jsx("span",{className:"nice-social-panel__label",children:s.label}),S&&e.jsx("span",{className:"nice-social-panel__count",children:k(s.count)})]},s.key))})}),Y=l.forwardRef(function(T,E){const{tags:m,onTagClick:n,minFontSize:o=12,maxFontSize:h=36,sortBy:p="weight",maxTags:r,selectedIds:y,className:S,style:C}=T,j=l.useMemo(()=>{let s=[...m];if(p==="weight")s.sort((u,f)=>f.weight-u.weight);else if(p==="alphabetical")s.sort((u,f)=>u.label.localeCompare(f.label));else for(let u=s.length-1;u>0;u--){const f=Math.floor(Math.random()*(u+1));[s[u],s[f]]=[s[f],s[u]]}return r&&s.length>r&&(s=s.slice(0,r)),s},[m,p,r]),{minW:b,maxW:k}=l.useMemo(()=>{if(j.length===0)return{minW:0,maxW:1};const s=j.map(u=>u.weight);return{minW:Math.min(...s),maxW:Math.max(...s)}},[j]),x=s=>{if(k===b)return(o+h)/2;const u=(s-b)/(k-b);return o+u*(h-o)};return e.jsx("div",{ref:E,className:`nice-tag-cloud ${S??""}`,style:C,children:j.length===0?e.jsx("p",{className:"nice-tag-cloud__empty",children:"No tags."}):j.map(s=>{const u=y==null?void 0:y.includes(s.id),f=x(s.weight);return e.jsx("span",{className:`nice-tag-cloud__tag ${u?"nice-tag-cloud__tag--selected":""}`,style:{fontSize:f,color:s.color,cursor:n?"pointer":void 0},onClick:()=>n==null?void 0:n(s),role:n?"button":void 0,tabIndex:n?0:void 0,onKeyDown:t=>{(t.key==="Enter"||t.key===" ")&&n&&n(s)},title:`${s.label} (${s.weight})`,children:s.label},s.id)})})}),G={online:"#22c55e",away:"#eab308",busy:"#ef4444",offline:"#9ca3af"},J=l.forwardRef(function(T,E){const{users:m,onUserClick:n,onAction:o,actions:h=[],searchable:p=!1,statusFilter:r,showStatus:y=!0,layout:S="list",groupByRole:C=!1,className:j,style:b}=T,[k,x]=l.useState(""),s=l.useMemo(()=>{let t=m;if(r&&r.length>0&&(t=t.filter(i=>i.status&&r.includes(i.status))),k.trim()){const i=k.toLowerCase();t=t.filter(c=>{var a,g;return c.name.toLowerCase().includes(i)||((a=c.email)==null?void 0:a.toLowerCase().includes(i))||((g=c.role)==null?void 0:g.toLowerCase().includes(i))})}return t},[m,r,k]),u=l.useMemo(()=>{if(!C)return{"":s};const t={};for(const i of s){const c=i.role??"Other";t[c]||(t[c]=[]),t[c].push(i)}return t},[s,C]),f=t=>e.jsxs("div",{className:`nice-user-list__item nice-user-list__item--${S}`,onClick:()=>n==null?void 0:n(t),role:n?"button":void 0,tabIndex:n?0:void 0,onKeyDown:i=>{i.key==="Enter"&&n&&n(t)},children:[e.jsxs("div",{className:"nice-user-list__avatar-wrap",children:[t.avatarUrl?e.jsx("img",{src:t.avatarUrl,alt:t.name,className:"nice-user-list__avatar"}):e.jsx("span",{className:"nice-user-list__avatar-placeholder",children:t.name.charAt(0).toUpperCase()}),y&&t.status&&e.jsx("span",{className:"nice-user-list__status-dot",style:{backgroundColor:G[t.status]},title:t.status})]}),e.jsxs("div",{className:"nice-user-list__info",children:[e.jsx("span",{className:"nice-user-list__name",children:t.name}),t.email&&e.jsx("span",{className:"nice-user-list__email",children:t.email}),t.role&&e.jsx("span",{className:"nice-user-list__role",children:t.role})]}),h.length>0&&e.jsx("div",{className:"nice-user-list__actions",children:h.map(i=>e.jsx("button",{className:"nice-user-list__action-btn",onClick:c=>{c.stopPropagation(),o==null||o(t,i)},children:i},i))})]},t.id);return e.jsxs("div",{ref:E,className:`nice-user-list nice-user-list--${S} ${j??""}`,style:b,children:[p&&e.jsx("input",{type:"text",value:k,onChange:t=>x(t.target.value),placeholder:"Search users…",className:"nice-user-list__search"}),s.length===0?e.jsx("p",{className:"nice-user-list__empty",children:"No users found."}):Object.entries(u).map(([t,i])=>e.jsxs("div",{className:"nice-user-list__group",children:[C&&t&&e.jsxs("h4",{className:"nice-user-list__group-title",children:[t," (",i.length,")"]}),i.map(f)]},t))]})}),Q=l.forwardRef(function(T,E){const{pages:m,selectedPageId:n,onSelectPage:o,onSavePage:h,onDeletePage:p,onCreatePage:r,editable:y=!1,searchable:S=!0,renderHtml:C=!1,sidebarWidth:j=260,className:b,style:k}=T,[x,s]=l.useState(""),[u,f]=l.useState(!1),[t,i]=l.useState(""),[c,a]=l.useState(""),[g,M]=l.useState(""),[L,R]=l.useState(!1),P=l.useMemo(()=>{if(m.some(v=>v.children&&v.children.length>0))return m.filter(v=>!v.parentId);const _=new Map;for(const v of m)_.set(v.id,{...v,children:[]});const N=[];for(const v of _.values())v.parentId&&_.has(v.parentId)?_.get(v.parentId).children.push(v):N.push(v);return N},[m]),w=l.useMemo(()=>m.find(_=>_.id===n),[m,n]),$=l.useMemo(()=>{if(!x.trim())return P;const _=x.toLowerCase(),N=new Set(m.filter(v=>v.title.toLowerCase().includes(_)).map(v=>v.id));return m.filter(v=>N.has(v.id))},[P,m,x]),D=l.useCallback(()=>{w&&(i(w.title),a(w.content??""),f(!0))},[w]),d=l.useCallback(()=>{!w||!h||(h(w.id,c,t),f(!1))},[w,c,t,h]),I=l.useCallback(()=>{!g.trim()||!r||(r(n??null,g.trim()),M(""),R(!1))},[g,n,r]),F=(_,N=0)=>{var v;return e.jsxs("div",{children:[e.jsx("div",{className:`nice-wiki__page-item ${_.id===n?"nice-wiki__page-item--active":""}`,style:{paddingLeft:12+N*16},onClick:()=>o==null?void 0:o(_),role:"button",tabIndex:0,onKeyDown:W=>{W.key==="Enter"&&(o==null||o(_))},children:_.title}),(v=_.children)==null?void 0:v.map(W=>F(W,N+1))]},_.id)};return e.jsxs("div",{ref:E,className:`nice-wiki ${b??""}`,style:{display:"flex",...k},children:[e.jsxs("div",{className:"nice-wiki__sidebar",style:{width:j,minWidth:j},children:[e.jsxs("div",{className:"nice-wiki__sidebar-header",children:[e.jsx("strong",{children:"Wiki"}),y&&r&&e.jsx("button",{onClick:()=>R(!L),title:"New page",children:"+"})]}),L&&e.jsxs("div",{className:"nice-wiki__new-page",children:[e.jsx("input",{type:"text",value:g,onChange:_=>M(_.target.value),placeholder:"New page title"}),e.jsx("button",{onClick:I,disabled:!g.trim(),children:"Create"})]}),S&&e.jsx("input",{type:"text",className:"nice-wiki__search",value:x,onChange:_=>s(_.target.value),placeholder:"Search pages…"}),e.jsx("div",{className:"nice-wiki__tree",children:$.length===0?e.jsx("p",{className:"nice-wiki__empty",children:"No pages."}):$.map(_=>F(_))})]}),e.jsx("div",{className:"nice-wiki__content",children:w?u?e.jsxs("div",{className:"nice-wiki__editor",children:[e.jsx("input",{type:"text",className:"nice-wiki__edit-title",value:t,onChange:_=>i(_.target.value)}),e.jsx("textarea",{className:"nice-wiki__edit-body",value:c,onChange:_=>a(_.target.value),rows:20}),e.jsxs("div",{className:"nice-wiki__edit-actions",children:[e.jsx("button",{onClick:d,children:"Save"}),e.jsx("button",{onClick:()=>f(!1),children:"Cancel"})]})]}):e.jsxs("div",{className:"nice-wiki__viewer",children:[e.jsxs("div",{className:"nice-wiki__viewer-header",children:[e.jsx("h2",{children:w.title}),e.jsxs("div",{className:"nice-wiki__viewer-meta",children:[w.lastEditedBy&&e.jsxs("span",{children:["Edited by ",w.lastEditedBy]}),w.lastEditedAt&&e.jsxs("span",{children:[" on ",w.lastEditedAt]})]}),y&&e.jsxs("div",{className:"nice-wiki__viewer-actions",children:[e.jsx("button",{onClick:D,children:"Edit"}),p&&e.jsx("button",{onClick:()=>p(w.id),children:"Delete"})]})]}),C?e.jsx("div",{className:"nice-wiki__viewer-body",dangerouslySetInnerHTML:{__html:w.content??""}}):e.jsx("div",{className:"nice-wiki__viewer-body",children:w.content??e.jsx("em",{children:"No content."})})]}):e.jsx("div",{className:"nice-wiki__placeholder",children:"Select a page from the sidebar."})})]})}),U={comment:"💬",like:"❤️",share:"↗️",create:"✨",update:"✏️",delete:"🗑️",join:"👋",leave:"🚪",upload:"📎",mention:"@",custom:"📌"},X=l.forwardRef(function(T,E){const{events:m,onEventClick:n,groupByDate:o=!0,typeFilter:h,showFilters:p=!1,maxEvents:r,onLoadMore:y,hasMore:S=!1,className:C,style:j}=T,[b,k]=l.useState(new Set),x=h??(b.size>0?Array.from(b):void 0),s=l.useMemo(()=>{let i=m;if(x&&x.length>0){const c=new Set(x);i=i.filter(a=>c.has(a.type))}return r&&(i=i.slice(0,r)),i},[m,x,r]),u=l.useMemo(()=>{if(!o)return[{label:"",events:s}];const i=new Map;for(const c of s){const a=c.timestamp.slice(0,10);i.has(a)||i.set(a,[]),i.get(a).push(c)}return Array.from(i.entries()).map(([c,a])=>({label:c,events:a}))},[s,o]),f=l.useMemo(()=>{const i=new Set(m.map(c=>c.type));return Array.from(i).sort()},[m]),t=i=>{k(c=>{const a=new Set(c);return a.has(i)?a.delete(i):a.add(i),a})};return e.jsxs("div",{ref:E,className:`nice-activity-feed ${C??""}`,style:j,children:[p&&!h&&e.jsxs("div",{className:"nice-activity-feed__filters",children:[f.map(i=>e.jsxs("button",{className:`nice-activity-feed__filter-chip ${b.has(i)?"nice-activity-feed__filter-chip--active":""}`,onClick:()=>t(i),children:[U[i]," ",i]},i)),b.size>0&&e.jsx("button",{className:"nice-activity-feed__filter-clear",onClick:()=>k(new Set),children:"Clear"})]}),s.length===0?e.jsx("p",{className:"nice-activity-feed__empty",children:"No activity."}):u.map(({label:i,events:c})=>e.jsxs("div",{className:"nice-activity-feed__group",children:[o&&i&&e.jsx("div",{className:"nice-activity-feed__date-label",children:i}),c.map(a=>e.jsxs("div",{className:"nice-activity-feed__event",onClick:()=>n==null?void 0:n(a),role:n?"button":void 0,tabIndex:n?0:void 0,onKeyDown:g=>{g.key==="Enter"&&n&&n(a)},children:[e.jsx("span",{className:"nice-activity-feed__icon",children:U[a.type]}),e.jsxs("div",{className:"nice-activity-feed__body",children:[a.actorAvatarUrl&&e.jsx("img",{src:a.actorAvatarUrl,alt:"",className:"nice-activity-feed__avatar"}),e.jsxs("span",{className:"nice-activity-feed__message",children:[e.jsx("strong",{children:a.actorName})," ",a.message,a.targetLabel&&e.jsxs(e.Fragment,{children:[" ",e.jsx("em",{children:a.targetLabel})]})]}),e.jsx("time",{className:"nice-activity-feed__time",children:a.timestamp})]})]},a.id))]},i||"__all")),S&&y&&e.jsx("button",{className:"nice-activity-feed__load-more",onClick:y,children:"Load more"})]})});exports.NiceActivityFeed=X;exports.NiceComments=O;exports.NiceRatings=q;exports.NiceSocialPanel=V;exports.NiceTagCloud=Y;exports.NiceUserList=J;exports.NiceWiki=Q;
@@ -0,0 +1,298 @@
1
+ import { default as default_2 } from 'react';
2
+
3
+ /** Single activity event. */
4
+ export declare interface ActivityEvent {
5
+ id: string;
6
+ type: ActivityType;
7
+ actorName: string;
8
+ actorAvatarUrl?: string;
9
+ message: string;
10
+ timestamp: string;
11
+ targetUrl?: string;
12
+ targetLabel?: string;
13
+ metadata?: Record<string, unknown>;
14
+ group?: string;
15
+ }
16
+
17
+ /** Activity type. */
18
+ export declare type ActivityType = 'comment' | 'like' | 'share' | 'create' | 'update' | 'delete' | 'join' | 'leave' | 'upload' | 'mention' | 'custom';
19
+
20
+ /** A single comment. */
21
+ export declare interface CommentEntry {
22
+ id: string;
23
+ authorId: string;
24
+ authorName: string;
25
+ authorAvatarUrl?: string;
26
+ content: string;
27
+ createdAt: string;
28
+ updatedAt?: string;
29
+ parentId?: string;
30
+ reactions?: Record<string, number>;
31
+ isEdited?: boolean;
32
+ isDeleted?: boolean;
33
+ }
34
+
35
+ /**
36
+ * {@link NiceActivityFeed} — Chronological activity stream with grouping and filtering.
37
+ */
38
+ export declare const NiceActivityFeed: default_2.ForwardRefExoticComponent<NiceActivityFeedProps & default_2.RefAttributes<HTMLDivElement>>;
39
+
40
+ /** Props for {@link NiceActivityFeed}. */
41
+ export declare interface NiceActivityFeedProps {
42
+ /** Activity events (newest first). */
43
+ events: ActivityEvent[];
44
+ /** Called when an event is clicked. */
45
+ onEventClick?: (event: ActivityEvent) => void;
46
+ /** Group events by date (day). */
47
+ groupByDate?: boolean;
48
+ /** Filter by activity types. */
49
+ typeFilter?: ActivityType[];
50
+ /** Whether to show filter chips. */
51
+ showFilters?: boolean;
52
+ /** Maximum events to display. */
53
+ maxEvents?: number;
54
+ /** Called when "load more" is clicked. */
55
+ onLoadMore?: () => void;
56
+ /** Whether more events are available. */
57
+ hasMore?: boolean;
58
+ className?: string;
59
+ style?: default_2.CSSProperties;
60
+ }
61
+
62
+ /**
63
+ * {@link NiceComments} — Threaded comment section with replies, reactions, and moderation.
64
+ */
65
+ export declare const NiceComments: default_2.ForwardRefExoticComponent<NiceCommentsProps & default_2.RefAttributes<HTMLDivElement>>;
66
+
67
+ /** Props for {@link NiceComments}. */
68
+ export declare interface NiceCommentsProps {
69
+ /** All comments (flat — parentId builds the tree). */
70
+ comments: CommentEntry[];
71
+ /** Current user ID (for highlighting own comments). */
72
+ currentUserId?: string;
73
+ /** Called when a new comment/reply is submitted. */
74
+ onSubmit?: (content: string, parentId?: string) => void;
75
+ /** Called when editing a comment. */
76
+ onEdit?: (commentId: string, content: string) => void;
77
+ /** Called when deleting a comment. */
78
+ onDelete?: (commentId: string) => void;
79
+ /** Called when adding a reaction. */
80
+ onReact?: (commentId: string, reaction: string) => void;
81
+ /** Whether new comments can be posted. */
82
+ readOnly?: boolean;
83
+ /** Maximum reply depth. */
84
+ maxDepth?: number;
85
+ /** Available reaction emojis. */
86
+ reactions?: string[];
87
+ /** Placeholder text for new comment input. */
88
+ placeholder?: string;
89
+ className?: string;
90
+ style?: default_2.CSSProperties;
91
+ }
92
+
93
+ /**
94
+ * {@link NiceRatings} — Star/emoji/score rating component with aggregation display.
95
+ */
96
+ export declare const NiceRatings: default_2.ForwardRefExoticComponent<NiceRatingsProps & default_2.RefAttributes<HTMLDivElement>>;
97
+
98
+ /** Props for {@link NiceRatings}. */
99
+ export declare interface NiceRatingsProps {
100
+ /** Current value (0–max). */
101
+ value?: number;
102
+ /** Maximum value (default 5). */
103
+ max?: number;
104
+ /** Display mode. */
105
+ mode?: RatingMode;
106
+ /** Aggregated data to show average/count. */
107
+ aggregation?: RatingAggregation;
108
+ /** Called when rating changes. */
109
+ onChange?: (value: number) => void;
110
+ /** If true, user can't change the rating. */
111
+ readOnly?: boolean;
112
+ /** Size variant. */
113
+ size?: 'sm' | 'md' | 'lg';
114
+ /** Show numeric value next to the visual. */
115
+ showValue?: boolean;
116
+ /** Label. */
117
+ label?: string;
118
+ /** Custom emoji set (for 'emoji' mode). */
119
+ emojiSet?: string[];
120
+ className?: string;
121
+ style?: default_2.CSSProperties;
122
+ }
123
+
124
+ /**
125
+ * {@link NiceSocialPanel} — Likes, shares, and bookmarks action panel.
126
+ */
127
+ export declare const NiceSocialPanel: default_2.ForwardRefExoticComponent<NiceSocialPanelProps & default_2.RefAttributes<HTMLDivElement>>;
128
+
129
+ /** Props for {@link NiceSocialPanel}. */
130
+ export declare interface NiceSocialPanelProps {
131
+ /** Aggregate stats. */
132
+ stats: SocialStats;
133
+ /** Current user's action state. */
134
+ userState?: SocialUserState;
135
+ /** Called when like is toggled. */
136
+ onLike?: () => void;
137
+ /** Called when share is clicked. */
138
+ onShare?: () => void;
139
+ /** Called when bookmark is toggled. */
140
+ onBookmark?: () => void;
141
+ /** Layout direction. */
142
+ direction?: 'horizontal' | 'vertical';
143
+ /** Size variant. */
144
+ size?: 'sm' | 'md' | 'lg';
145
+ /** Show counts. */
146
+ showCounts?: boolean;
147
+ /** Show labels. */
148
+ showLabels?: boolean;
149
+ className?: string;
150
+ style?: default_2.CSSProperties;
151
+ }
152
+
153
+ /**
154
+ * {@link NiceTagCloud} — Weighted tag cloud with click handling and sizing.
155
+ */
156
+ export declare const NiceTagCloud: default_2.ForwardRefExoticComponent<NiceTagCloudProps & default_2.RefAttributes<HTMLDivElement>>;
157
+
158
+ /** Props for {@link NiceTagCloud}. */
159
+ export declare interface NiceTagCloudProps {
160
+ /** Tags with weights. */
161
+ tags: TagEntry[];
162
+ /** Called when a tag is clicked. */
163
+ onTagClick?: (tag: TagEntry) => void;
164
+ /** Minimum font size in px. */
165
+ minFontSize?: number;
166
+ /** Maximum font size in px. */
167
+ maxFontSize?: number;
168
+ /** Sort mode. */
169
+ sortBy?: 'weight' | 'alphabetical' | 'random';
170
+ /** Maximum number of tags to display. */
171
+ maxTags?: number;
172
+ /** Currently selected tag IDs. */
173
+ selectedIds?: string[];
174
+ className?: string;
175
+ style?: default_2.CSSProperties;
176
+ }
177
+
178
+ /**
179
+ * {@link NiceUserList} — User list with avatars, online status, and filtering.
180
+ */
181
+ export declare const NiceUserList: default_2.ForwardRefExoticComponent<NiceUserListProps & default_2.RefAttributes<HTMLDivElement>>;
182
+
183
+ /** Props for {@link NiceUserList}. */
184
+ export declare interface NiceUserListProps {
185
+ /** Users. */
186
+ users: UserEntry[];
187
+ /** Called when a user is clicked. */
188
+ onUserClick?: (user: UserEntry) => void;
189
+ /** Called for actions (e.g., message, remove). */
190
+ onAction?: (user: UserEntry, action: string) => void;
191
+ /** Available actions per user. */
192
+ actions?: string[];
193
+ /** Whether to show search input. */
194
+ searchable?: boolean;
195
+ /** Filter by online status. */
196
+ statusFilter?: UserOnlineStatus[];
197
+ /** Whether to show the status indicator dot. */
198
+ showStatus?: boolean;
199
+ /** Render mode. */
200
+ layout?: 'list' | 'grid';
201
+ /** Group by role. */
202
+ groupByRole?: boolean;
203
+ className?: string;
204
+ style?: default_2.CSSProperties;
205
+ }
206
+
207
+ /**
208
+ * {@link NiceWiki} — Wiki module with page tree sidebar, content viewer/editor.
209
+ */
210
+ export declare const NiceWiki: default_2.ForwardRefExoticComponent<NiceWikiProps & default_2.RefAttributes<HTMLDivElement>>;
211
+
212
+ /** Props for {@link NiceWiki}. */
213
+ export declare interface NiceWikiProps {
214
+ /** All pages (flat or nested via children). */
215
+ pages: WikiPage[];
216
+ /** Currently selected page ID. */
217
+ selectedPageId?: string;
218
+ /** Called when selecting a page. */
219
+ onSelectPage?: (page: WikiPage) => void;
220
+ /** Called to save page content. */
221
+ onSavePage?: (pageId: string, content: string, title: string) => void;
222
+ /** Called to create a new page. */
223
+ onCreatePage?: (parentId: string | null, title: string) => void;
224
+ /** Called to delete a page. */
225
+ onDeletePage?: (pageId: string) => void;
226
+ /** Whether editing is allowed. */
227
+ editable?: boolean;
228
+ /** Whether to show search. */
229
+ searchable?: boolean;
230
+ /** Render content as HTML (dangerouslySetInnerHTML). */
231
+ renderHtml?: boolean;
232
+ /** Sidebar width. */
233
+ sidebarWidth?: number;
234
+ className?: string;
235
+ style?: default_2.CSSProperties;
236
+ }
237
+
238
+ /** Aggregated rating data. */
239
+ export declare interface RatingAggregation {
240
+ average: number;
241
+ count: number;
242
+ distribution?: Record<number, number>;
243
+ }
244
+
245
+ /** Rating display mode. */
246
+ export declare type RatingMode = 'stars' | 'hearts' | 'thumbs' | 'numeric' | 'emoji';
247
+
248
+ /** Social action stats. */
249
+ export declare interface SocialStats {
250
+ likes: number;
251
+ shares: number;
252
+ bookmarks: number;
253
+ }
254
+
255
+ /** Current user's state for social actions. */
256
+ export declare interface SocialUserState {
257
+ liked: boolean;
258
+ shared: boolean;
259
+ bookmarked: boolean;
260
+ }
261
+
262
+ /** A weighted tag. */
263
+ export declare interface TagEntry {
264
+ id: string;
265
+ label: string;
266
+ weight: number;
267
+ color?: string;
268
+ href?: string;
269
+ }
270
+
271
+ /** A user entry. */
272
+ export declare interface UserEntry {
273
+ id: string;
274
+ name: string;
275
+ email?: string;
276
+ avatarUrl?: string;
277
+ status?: UserOnlineStatus;
278
+ role?: string;
279
+ lastSeenAt?: string;
280
+ extra?: Record<string, string>;
281
+ }
282
+
283
+ /** User online status. */
284
+ export declare type UserOnlineStatus = 'online' | 'away' | 'busy' | 'offline';
285
+
286
+ /** Wiki page tree node. */
287
+ export declare interface WikiPage {
288
+ id: string;
289
+ title: string;
290
+ slug: string;
291
+ parentId?: string;
292
+ content?: string;
293
+ lastEditedBy?: string;
294
+ lastEditedAt?: string;
295
+ children?: WikiPage[];
296
+ }
297
+
298
+ export { }
package/dist/index.mjs ADDED
@@ -0,0 +1,660 @@
1
+ import { jsxs as c, jsx as e, Fragment as H } from "react/jsx-runtime";
2
+ import { forwardRef as D, useState as T, useCallback as P, useMemo as F } from "react";
3
+ const Z = D(function(I, L) {
4
+ const {
5
+ comments: m,
6
+ currentUserId: a,
7
+ onSubmit: o,
8
+ onEdit: _,
9
+ onDelete: N,
10
+ onReact: r,
11
+ readOnly: k = !1,
12
+ maxDepth: E = 4,
13
+ reactions: x = ["👍", "❤️", "😂", "😮", "😢"],
14
+ placeholder: y = "Write a comment…",
15
+ className: w,
16
+ style: C
17
+ } = I, [v, i] = T(""), [h, f] = T(null), [n, t] = T(""), [l, s] = T(null), [b, $] = T(""), A = /* @__PURE__ */ new Map();
18
+ for (const d of m) {
19
+ const M = d.parentId ?? "__root__";
20
+ A.has(M) || A.set(M, []), A.get(M).push(d);
21
+ }
22
+ const O = P(() => {
23
+ !v.trim() || !o || (o(v.trim()), i(""));
24
+ }, [v, o]), z = P(() => {
25
+ !n.trim() || !o || !h || (o(n.trim(), h), t(""), f(null));
26
+ }, [n, h, o]), S = P(() => {
27
+ !l || !b.trim() || !_ || (_(l, b.trim()), s(null), $(""));
28
+ }, [l, b, _]), W = (d, M) => {
29
+ const U = a && d.authorId === a, u = A.get(d.id) ?? [];
30
+ return /* @__PURE__ */ c("div", { className: "nice-comments__item", style: { marginLeft: M * 24 }, children: [
31
+ /* @__PURE__ */ c("div", { className: "nice-comments__header", children: [
32
+ d.authorAvatarUrl && /* @__PURE__ */ e("img", { src: d.authorAvatarUrl, alt: "", className: "nice-comments__avatar" }),
33
+ /* @__PURE__ */ e("strong", { className: "nice-comments__author", children: d.authorName }),
34
+ /* @__PURE__ */ e("time", { className: "nice-comments__time", children: d.createdAt }),
35
+ d.isEdited && /* @__PURE__ */ e("span", { className: "nice-comments__edited", children: "(edited)" })
36
+ ] }),
37
+ l === d.id ? /* @__PURE__ */ c("div", { className: "nice-comments__edit-form", children: [
38
+ /* @__PURE__ */ e("textarea", { value: b, onChange: (g) => $(g.target.value) }),
39
+ /* @__PURE__ */ e("button", { onClick: S, children: "Save" }),
40
+ /* @__PURE__ */ e("button", { onClick: () => s(null), children: "Cancel" })
41
+ ] }) : /* @__PURE__ */ e("p", { className: "nice-comments__content", children: d.isDeleted ? /* @__PURE__ */ e("em", { children: "Comment deleted" }) : d.content }),
42
+ !d.isDeleted && d.reactions && /* @__PURE__ */ e("div", { className: "nice-comments__reactions", children: Object.entries(d.reactions).map(([g, p]) => /* @__PURE__ */ c(
43
+ "button",
44
+ {
45
+ className: "nice-comments__reaction",
46
+ onClick: () => r == null ? void 0 : r(d.id, g),
47
+ children: [
48
+ g,
49
+ " ",
50
+ p
51
+ ]
52
+ },
53
+ g
54
+ )) }),
55
+ !d.isDeleted && !k && /* @__PURE__ */ c("div", { className: "nice-comments__actions", children: [
56
+ r && x.map((g) => /* @__PURE__ */ e("button", { onClick: () => r(d.id, g), title: g, children: g }, g)),
57
+ M < E && /* @__PURE__ */ e("button", { onClick: () => {
58
+ f(d.id), t("");
59
+ }, children: "Reply" }),
60
+ U && _ && /* @__PURE__ */ e("button", { onClick: () => {
61
+ s(d.id), $(d.content);
62
+ }, children: "Edit" }),
63
+ U && N && /* @__PURE__ */ e("button", { onClick: () => N(d.id), children: "Delete" })
64
+ ] }),
65
+ h === d.id && /* @__PURE__ */ c("div", { className: "nice-comments__reply-form", children: [
66
+ /* @__PURE__ */ e(
67
+ "textarea",
68
+ {
69
+ value: n,
70
+ onChange: (g) => t(g.target.value),
71
+ placeholder: "Write a reply…"
72
+ }
73
+ ),
74
+ /* @__PURE__ */ e("button", { onClick: z, disabled: !n.trim(), children: "Send" }),
75
+ /* @__PURE__ */ e("button", { onClick: () => f(null), children: "Cancel" })
76
+ ] }),
77
+ u.map((g) => W(g, M + 1))
78
+ ] }, d.id);
79
+ }, j = A.get("__root__") ?? [];
80
+ return /* @__PURE__ */ c("div", { ref: L, className: `nice-comments ${w ?? ""}`, style: C, children: [
81
+ !k && /* @__PURE__ */ c("div", { className: "nice-comments__new", children: [
82
+ /* @__PURE__ */ e(
83
+ "textarea",
84
+ {
85
+ value: v,
86
+ onChange: (d) => i(d.target.value),
87
+ placeholder: y
88
+ }
89
+ ),
90
+ /* @__PURE__ */ e("button", { onClick: O, disabled: !v.trim(), children: "Post" })
91
+ ] }),
92
+ j.length === 0 ? /* @__PURE__ */ e("p", { className: "nice-comments__empty", children: "No comments yet." }) : j.map((d) => W(d, 0))
93
+ ] });
94
+ }), V = "☆", Y = "★", q = "♡", G = "♥", ee = D(function(I, L) {
95
+ const {
96
+ value: m,
97
+ max: a = 5,
98
+ mode: o = "stars",
99
+ aggregation: _,
100
+ onChange: N,
101
+ readOnly: r = !1,
102
+ size: k = "md",
103
+ showValue: E = !1,
104
+ label: x,
105
+ emojiSet: y = ["😢", "😕", "😐", "🙂", "😍"],
106
+ className: w,
107
+ style: C
108
+ } = I, [v, i] = T(null), h = v !== null ? v + 1 : m ?? 0, f = P((l) => {
109
+ r || !N || N(l + 1);
110
+ }, [r, N]), n = () => {
111
+ const l = [];
112
+ for (let s = 0; s < a; s++) {
113
+ const b = s < h;
114
+ let $;
115
+ o === "emoji" ? $ = y[s] ?? y[y.length - 1] : o === "hearts" ? $ = b ? G : q : o === "thumbs" ? $ = b ? "👍" : "👎" : $ = b ? Y : V, l.push(
116
+ /* @__PURE__ */ e(
117
+ "span",
118
+ {
119
+ className: `nice-ratings__symbol ${b ? "nice-ratings__symbol--active" : ""}`,
120
+ onClick: () => f(s),
121
+ onMouseEnter: () => !r && i(s),
122
+ onMouseLeave: () => !r && i(null),
123
+ role: r ? void 0 : "button",
124
+ tabIndex: r ? void 0 : 0,
125
+ onKeyDown: (A) => {
126
+ (A.key === "Enter" || A.key === " ") && f(s);
127
+ },
128
+ children: $
129
+ },
130
+ s
131
+ )
132
+ );
133
+ }
134
+ return l;
135
+ }, t = () => /* @__PURE__ */ c("div", { className: "nice-ratings__numeric", children: [
136
+ /* @__PURE__ */ e(
137
+ "input",
138
+ {
139
+ type: "number",
140
+ min: 0,
141
+ max: a,
142
+ step: 0.5,
143
+ value: m ?? 0,
144
+ onChange: (l) => N == null ? void 0 : N(parseFloat(l.target.value) || 0),
145
+ readOnly: r,
146
+ className: "nice-ratings__numeric-input"
147
+ }
148
+ ),
149
+ /* @__PURE__ */ c("span", { className: "nice-ratings__numeric-max", children: [
150
+ "/ ",
151
+ a
152
+ ] })
153
+ ] });
154
+ return /* @__PURE__ */ c(
155
+ "div",
156
+ {
157
+ ref: L,
158
+ className: `nice-ratings nice-ratings--${k} nice-ratings--${o} ${r ? "nice-ratings--readonly" : ""} ${w ?? ""}`,
159
+ style: C,
160
+ children: [
161
+ x && /* @__PURE__ */ e("span", { className: "nice-ratings__label", children: x }),
162
+ /* @__PURE__ */ c("div", { className: "nice-ratings__body", children: [
163
+ o === "numeric" ? t() : n(),
164
+ E && o !== "numeric" && /* @__PURE__ */ c("span", { className: "nice-ratings__value", children: [
165
+ h,
166
+ " / ",
167
+ a
168
+ ] })
169
+ ] }),
170
+ _ && /* @__PURE__ */ c("div", { className: "nice-ratings__aggregation", children: [
171
+ /* @__PURE__ */ e("span", { className: "nice-ratings__avg", children: _.average.toFixed(1) }),
172
+ /* @__PURE__ */ c("span", { className: "nice-ratings__count", children: [
173
+ "(",
174
+ _.count,
175
+ " ratings)"
176
+ ] }),
177
+ _.distribution && /* @__PURE__ */ e("div", { className: "nice-ratings__distribution", children: Object.entries(_.distribution).sort(([l], [s]) => +s - +l).map(([l, s]) => /* @__PURE__ */ c("div", { className: "nice-ratings__distribution-row", children: [
178
+ /* @__PURE__ */ e("span", { children: l }),
179
+ /* @__PURE__ */ e("div", { className: "nice-ratings__bar", children: /* @__PURE__ */ e(
180
+ "div",
181
+ {
182
+ className: "nice-ratings__bar-fill",
183
+ style: { width: `${_.count ? s / _.count * 100 : 0}%` }
184
+ }
185
+ ) }),
186
+ /* @__PURE__ */ e("span", { children: s })
187
+ ] }, l)) })
188
+ ] })
189
+ ]
190
+ }
191
+ );
192
+ }), ie = D(function(I, L) {
193
+ const {
194
+ stats: m,
195
+ userState: a,
196
+ onLike: o,
197
+ onShare: _,
198
+ onBookmark: N,
199
+ direction: r = "horizontal",
200
+ size: k = "md",
201
+ showCounts: E = !0,
202
+ showLabels: x = !1,
203
+ className: y,
204
+ style: w
205
+ } = I, C = P((i) => i >= 1e6 ? (i / 1e6).toFixed(1) + "M" : i >= 1e3 ? (i / 1e3).toFixed(1) + "K" : i.toString(), []), v = [
206
+ {
207
+ key: "like",
208
+ icon: "♡",
209
+ activeIcon: "♥",
210
+ label: "Like",
211
+ count: m.likes,
212
+ isActive: (a == null ? void 0 : a.liked) ?? !1,
213
+ onClick: o
214
+ },
215
+ {
216
+ key: "share",
217
+ icon: "↗",
218
+ activeIcon: "↗",
219
+ label: "Share",
220
+ count: m.shares,
221
+ isActive: (a == null ? void 0 : a.shared) ?? !1,
222
+ onClick: _
223
+ },
224
+ {
225
+ key: "bookmark",
226
+ icon: "☆",
227
+ activeIcon: "★",
228
+ label: "Bookmark",
229
+ count: m.bookmarks,
230
+ isActive: (a == null ? void 0 : a.bookmarked) ?? !1,
231
+ onClick: N
232
+ }
233
+ ];
234
+ return /* @__PURE__ */ e(
235
+ "div",
236
+ {
237
+ ref: L,
238
+ className: `nice-social-panel nice-social-panel--${r} nice-social-panel--${k} ${y ?? ""}`,
239
+ style: w,
240
+ children: v.map((i) => /* @__PURE__ */ c(
241
+ "button",
242
+ {
243
+ className: `nice-social-panel__btn nice-social-panel__btn--${i.key} ${i.isActive ? "nice-social-panel__btn--active" : ""}`,
244
+ onClick: i.onClick,
245
+ title: i.label,
246
+ children: [
247
+ /* @__PURE__ */ e("span", { className: "nice-social-panel__icon", children: i.isActive ? i.activeIcon : i.icon }),
248
+ x && /* @__PURE__ */ e("span", { className: "nice-social-panel__label", children: i.label }),
249
+ E && /* @__PURE__ */ e("span", { className: "nice-social-panel__count", children: C(i.count) })
250
+ ]
251
+ },
252
+ i.key
253
+ ))
254
+ }
255
+ );
256
+ }), te = D(function(I, L) {
257
+ const {
258
+ tags: m,
259
+ onTagClick: a,
260
+ minFontSize: o = 12,
261
+ maxFontSize: _ = 36,
262
+ sortBy: N = "weight",
263
+ maxTags: r,
264
+ selectedIds: k,
265
+ className: E,
266
+ style: x
267
+ } = I, y = F(() => {
268
+ let i = [...m];
269
+ if (N === "weight")
270
+ i.sort((h, f) => f.weight - h.weight);
271
+ else if (N === "alphabetical")
272
+ i.sort((h, f) => h.label.localeCompare(f.label));
273
+ else
274
+ for (let h = i.length - 1; h > 0; h--) {
275
+ const f = Math.floor(Math.random() * (h + 1));
276
+ [i[h], i[f]] = [i[f], i[h]];
277
+ }
278
+ return r && i.length > r && (i = i.slice(0, r)), i;
279
+ }, [m, N, r]), { minW: w, maxW: C } = F(() => {
280
+ if (y.length === 0) return { minW: 0, maxW: 1 };
281
+ const i = y.map((h) => h.weight);
282
+ return { minW: Math.min(...i), maxW: Math.max(...i) };
283
+ }, [y]), v = (i) => {
284
+ if (C === w) return (o + _) / 2;
285
+ const h = (i - w) / (C - w);
286
+ return o + h * (_ - o);
287
+ };
288
+ return /* @__PURE__ */ e("div", { ref: L, className: `nice-tag-cloud ${E ?? ""}`, style: x, children: y.length === 0 ? /* @__PURE__ */ e("p", { className: "nice-tag-cloud__empty", children: "No tags." }) : y.map((i) => {
289
+ const h = k == null ? void 0 : k.includes(i.id), f = v(i.weight);
290
+ return /* @__PURE__ */ e(
291
+ "span",
292
+ {
293
+ className: `nice-tag-cloud__tag ${h ? "nice-tag-cloud__tag--selected" : ""}`,
294
+ style: {
295
+ fontSize: f,
296
+ color: i.color,
297
+ cursor: a ? "pointer" : void 0
298
+ },
299
+ onClick: () => a == null ? void 0 : a(i),
300
+ role: a ? "button" : void 0,
301
+ tabIndex: a ? 0 : void 0,
302
+ onKeyDown: (n) => {
303
+ (n.key === "Enter" || n.key === " ") && a && a(i);
304
+ },
305
+ title: `${i.label} (${i.weight})`,
306
+ children: i.label
307
+ },
308
+ i.id
309
+ );
310
+ }) });
311
+ }), J = {
312
+ online: "#22c55e",
313
+ away: "#eab308",
314
+ busy: "#ef4444",
315
+ offline: "#9ca3af"
316
+ }, ne = D(function(I, L) {
317
+ const {
318
+ users: m,
319
+ onUserClick: a,
320
+ onAction: o,
321
+ actions: _ = [],
322
+ searchable: N = !1,
323
+ statusFilter: r,
324
+ showStatus: k = !0,
325
+ layout: E = "list",
326
+ groupByRole: x = !1,
327
+ className: y,
328
+ style: w
329
+ } = I, [C, v] = T(""), i = F(() => {
330
+ let n = m;
331
+ if (r && r.length > 0 && (n = n.filter((t) => t.status && r.includes(t.status))), C.trim()) {
332
+ const t = C.toLowerCase();
333
+ n = n.filter(
334
+ (l) => {
335
+ var s, b;
336
+ return l.name.toLowerCase().includes(t) || ((s = l.email) == null ? void 0 : s.toLowerCase().includes(t)) || ((b = l.role) == null ? void 0 : b.toLowerCase().includes(t));
337
+ }
338
+ );
339
+ }
340
+ return n;
341
+ }, [m, r, C]), h = F(() => {
342
+ if (!x) return { "": i };
343
+ const n = {};
344
+ for (const t of i) {
345
+ const l = t.role ?? "Other";
346
+ n[l] || (n[l] = []), n[l].push(t);
347
+ }
348
+ return n;
349
+ }, [i, x]), f = (n) => /* @__PURE__ */ c(
350
+ "div",
351
+ {
352
+ className: `nice-user-list__item nice-user-list__item--${E}`,
353
+ onClick: () => a == null ? void 0 : a(n),
354
+ role: a ? "button" : void 0,
355
+ tabIndex: a ? 0 : void 0,
356
+ onKeyDown: (t) => {
357
+ t.key === "Enter" && a && a(n);
358
+ },
359
+ children: [
360
+ /* @__PURE__ */ c("div", { className: "nice-user-list__avatar-wrap", children: [
361
+ n.avatarUrl ? /* @__PURE__ */ e("img", { src: n.avatarUrl, alt: n.name, className: "nice-user-list__avatar" }) : /* @__PURE__ */ e("span", { className: "nice-user-list__avatar-placeholder", children: n.name.charAt(0).toUpperCase() }),
362
+ k && n.status && /* @__PURE__ */ e(
363
+ "span",
364
+ {
365
+ className: "nice-user-list__status-dot",
366
+ style: { backgroundColor: J[n.status] },
367
+ title: n.status
368
+ }
369
+ )
370
+ ] }),
371
+ /* @__PURE__ */ c("div", { className: "nice-user-list__info", children: [
372
+ /* @__PURE__ */ e("span", { className: "nice-user-list__name", children: n.name }),
373
+ n.email && /* @__PURE__ */ e("span", { className: "nice-user-list__email", children: n.email }),
374
+ n.role && /* @__PURE__ */ e("span", { className: "nice-user-list__role", children: n.role })
375
+ ] }),
376
+ _.length > 0 && /* @__PURE__ */ e("div", { className: "nice-user-list__actions", children: _.map((t) => /* @__PURE__ */ e(
377
+ "button",
378
+ {
379
+ className: "nice-user-list__action-btn",
380
+ onClick: (l) => {
381
+ l.stopPropagation(), o == null || o(n, t);
382
+ },
383
+ children: t
384
+ },
385
+ t
386
+ )) })
387
+ ]
388
+ },
389
+ n.id
390
+ );
391
+ return /* @__PURE__ */ c("div", { ref: L, className: `nice-user-list nice-user-list--${E} ${y ?? ""}`, style: w, children: [
392
+ N && /* @__PURE__ */ e(
393
+ "input",
394
+ {
395
+ type: "text",
396
+ value: C,
397
+ onChange: (n) => v(n.target.value),
398
+ placeholder: "Search users…",
399
+ className: "nice-user-list__search"
400
+ }
401
+ ),
402
+ i.length === 0 ? /* @__PURE__ */ e("p", { className: "nice-user-list__empty", children: "No users found." }) : Object.entries(h).map(([n, t]) => /* @__PURE__ */ c("div", { className: "nice-user-list__group", children: [
403
+ x && n && /* @__PURE__ */ c("h4", { className: "nice-user-list__group-title", children: [
404
+ n,
405
+ " (",
406
+ t.length,
407
+ ")"
408
+ ] }),
409
+ t.map(f)
410
+ ] }, n))
411
+ ] });
412
+ }), ae = D(function(I, L) {
413
+ const {
414
+ pages: m,
415
+ selectedPageId: a,
416
+ onSelectPage: o,
417
+ onSavePage: _,
418
+ onDeletePage: N,
419
+ onCreatePage: r,
420
+ editable: k = !1,
421
+ searchable: E = !0,
422
+ renderHtml: x = !1,
423
+ sidebarWidth: y = 260,
424
+ className: w,
425
+ style: C
426
+ } = I, [v, i] = T(""), [h, f] = T(!1), [n, t] = T(""), [l, s] = T(""), [b, $] = T(""), [A, O] = T(!1), z = F(() => {
427
+ if (m.some((p) => p.children && p.children.length > 0)) return m.filter((p) => !p.parentId);
428
+ const u = /* @__PURE__ */ new Map();
429
+ for (const p of m)
430
+ u.set(p.id, { ...p, children: [] });
431
+ const g = [];
432
+ for (const p of u.values())
433
+ p.parentId && u.has(p.parentId) ? u.get(p.parentId).children.push(p) : g.push(p);
434
+ return g;
435
+ }, [m]), S = F(() => m.find((u) => u.id === a), [m, a]), W = F(() => {
436
+ if (!v.trim()) return z;
437
+ const u = v.toLowerCase(), g = new Set(m.filter((p) => p.title.toLowerCase().includes(u)).map((p) => p.id));
438
+ return m.filter((p) => g.has(p.id));
439
+ }, [z, m, v]), j = P(() => {
440
+ S && (t(S.title), s(S.content ?? ""), f(!0));
441
+ }, [S]), d = P(() => {
442
+ !S || !_ || (_(S.id, l, n), f(!1));
443
+ }, [S, l, n, _]), M = P(() => {
444
+ !b.trim() || !r || (r(a ?? null, b.trim()), $(""), O(!1));
445
+ }, [b, a, r]), U = (u, g = 0) => {
446
+ var p;
447
+ return /* @__PURE__ */ c("div", { children: [
448
+ /* @__PURE__ */ e(
449
+ "div",
450
+ {
451
+ className: `nice-wiki__page-item ${u.id === a ? "nice-wiki__page-item--active" : ""}`,
452
+ style: { paddingLeft: 12 + g * 16 },
453
+ onClick: () => o == null ? void 0 : o(u),
454
+ role: "button",
455
+ tabIndex: 0,
456
+ onKeyDown: (B) => {
457
+ B.key === "Enter" && (o == null || o(u));
458
+ },
459
+ children: u.title
460
+ }
461
+ ),
462
+ (p = u.children) == null ? void 0 : p.map((B) => U(B, g + 1))
463
+ ] }, u.id);
464
+ };
465
+ return /* @__PURE__ */ c("div", { ref: L, className: `nice-wiki ${w ?? ""}`, style: { display: "flex", ...C }, children: [
466
+ /* @__PURE__ */ c("div", { className: "nice-wiki__sidebar", style: { width: y, minWidth: y }, children: [
467
+ /* @__PURE__ */ c("div", { className: "nice-wiki__sidebar-header", children: [
468
+ /* @__PURE__ */ e("strong", { children: "Wiki" }),
469
+ k && r && /* @__PURE__ */ e("button", { onClick: () => O(!A), title: "New page", children: "+" })
470
+ ] }),
471
+ A && /* @__PURE__ */ c("div", { className: "nice-wiki__new-page", children: [
472
+ /* @__PURE__ */ e(
473
+ "input",
474
+ {
475
+ type: "text",
476
+ value: b,
477
+ onChange: (u) => $(u.target.value),
478
+ placeholder: "New page title"
479
+ }
480
+ ),
481
+ /* @__PURE__ */ e("button", { onClick: M, disabled: !b.trim(), children: "Create" })
482
+ ] }),
483
+ E && /* @__PURE__ */ e(
484
+ "input",
485
+ {
486
+ type: "text",
487
+ className: "nice-wiki__search",
488
+ value: v,
489
+ onChange: (u) => i(u.target.value),
490
+ placeholder: "Search pages…"
491
+ }
492
+ ),
493
+ /* @__PURE__ */ e("div", { className: "nice-wiki__tree", children: W.length === 0 ? /* @__PURE__ */ e("p", { className: "nice-wiki__empty", children: "No pages." }) : W.map((u) => U(u)) })
494
+ ] }),
495
+ /* @__PURE__ */ e("div", { className: "nice-wiki__content", children: S ? h ? /* @__PURE__ */ c("div", { className: "nice-wiki__editor", children: [
496
+ /* @__PURE__ */ e(
497
+ "input",
498
+ {
499
+ type: "text",
500
+ className: "nice-wiki__edit-title",
501
+ value: n,
502
+ onChange: (u) => t(u.target.value)
503
+ }
504
+ ),
505
+ /* @__PURE__ */ e(
506
+ "textarea",
507
+ {
508
+ className: "nice-wiki__edit-body",
509
+ value: l,
510
+ onChange: (u) => s(u.target.value),
511
+ rows: 20
512
+ }
513
+ ),
514
+ /* @__PURE__ */ c("div", { className: "nice-wiki__edit-actions", children: [
515
+ /* @__PURE__ */ e("button", { onClick: d, children: "Save" }),
516
+ /* @__PURE__ */ e("button", { onClick: () => f(!1), children: "Cancel" })
517
+ ] })
518
+ ] }) : /* @__PURE__ */ c("div", { className: "nice-wiki__viewer", children: [
519
+ /* @__PURE__ */ c("div", { className: "nice-wiki__viewer-header", children: [
520
+ /* @__PURE__ */ e("h2", { children: S.title }),
521
+ /* @__PURE__ */ c("div", { className: "nice-wiki__viewer-meta", children: [
522
+ S.lastEditedBy && /* @__PURE__ */ c("span", { children: [
523
+ "Edited by ",
524
+ S.lastEditedBy
525
+ ] }),
526
+ S.lastEditedAt && /* @__PURE__ */ c("span", { children: [
527
+ " on ",
528
+ S.lastEditedAt
529
+ ] })
530
+ ] }),
531
+ k && /* @__PURE__ */ c("div", { className: "nice-wiki__viewer-actions", children: [
532
+ /* @__PURE__ */ e("button", { onClick: j, children: "Edit" }),
533
+ N && /* @__PURE__ */ e("button", { onClick: () => N(S.id), children: "Delete" })
534
+ ] })
535
+ ] }),
536
+ x ? /* @__PURE__ */ e(
537
+ "div",
538
+ {
539
+ className: "nice-wiki__viewer-body",
540
+ dangerouslySetInnerHTML: { __html: S.content ?? "" }
541
+ }
542
+ ) : /* @__PURE__ */ e("div", { className: "nice-wiki__viewer-body", children: S.content ?? /* @__PURE__ */ e("em", { children: "No content." }) })
543
+ ] }) : /* @__PURE__ */ e("div", { className: "nice-wiki__placeholder", children: "Select a page from the sidebar." }) })
544
+ ] });
545
+ }), K = {
546
+ comment: "💬",
547
+ like: "❤️",
548
+ share: "↗️",
549
+ create: "✨",
550
+ update: "✏️",
551
+ delete: "🗑️",
552
+ join: "👋",
553
+ leave: "🚪",
554
+ upload: "📎",
555
+ mention: "@",
556
+ custom: "📌"
557
+ }, se = D(function(I, L) {
558
+ const {
559
+ events: m,
560
+ onEventClick: a,
561
+ groupByDate: o = !0,
562
+ typeFilter: _,
563
+ showFilters: N = !1,
564
+ maxEvents: r,
565
+ onLoadMore: k,
566
+ hasMore: E = !1,
567
+ className: x,
568
+ style: y
569
+ } = I, [w, C] = T(/* @__PURE__ */ new Set()), v = _ ?? (w.size > 0 ? Array.from(w) : void 0), i = F(() => {
570
+ let t = m;
571
+ if (v && v.length > 0) {
572
+ const l = new Set(v);
573
+ t = t.filter((s) => l.has(s.type));
574
+ }
575
+ return r && (t = t.slice(0, r)), t;
576
+ }, [m, v, r]), h = F(() => {
577
+ if (!o) return [{ label: "", events: i }];
578
+ const t = /* @__PURE__ */ new Map();
579
+ for (const l of i) {
580
+ const s = l.timestamp.slice(0, 10);
581
+ t.has(s) || t.set(s, []), t.get(s).push(l);
582
+ }
583
+ return Array.from(t.entries()).map(([l, s]) => ({ label: l, events: s }));
584
+ }, [i, o]), f = F(() => {
585
+ const t = new Set(m.map((l) => l.type));
586
+ return Array.from(t).sort();
587
+ }, [m]), n = (t) => {
588
+ C((l) => {
589
+ const s = new Set(l);
590
+ return s.has(t) ? s.delete(t) : s.add(t), s;
591
+ });
592
+ };
593
+ return /* @__PURE__ */ c("div", { ref: L, className: `nice-activity-feed ${x ?? ""}`, style: y, children: [
594
+ N && !_ && /* @__PURE__ */ c("div", { className: "nice-activity-feed__filters", children: [
595
+ f.map((t) => /* @__PURE__ */ c(
596
+ "button",
597
+ {
598
+ className: `nice-activity-feed__filter-chip ${w.has(t) ? "nice-activity-feed__filter-chip--active" : ""}`,
599
+ onClick: () => n(t),
600
+ children: [
601
+ K[t],
602
+ " ",
603
+ t
604
+ ]
605
+ },
606
+ t
607
+ )),
608
+ w.size > 0 && /* @__PURE__ */ e(
609
+ "button",
610
+ {
611
+ className: "nice-activity-feed__filter-clear",
612
+ onClick: () => C(/* @__PURE__ */ new Set()),
613
+ children: "Clear"
614
+ }
615
+ )
616
+ ] }),
617
+ i.length === 0 ? /* @__PURE__ */ e("p", { className: "nice-activity-feed__empty", children: "No activity." }) : h.map(({ label: t, events: l }) => /* @__PURE__ */ c("div", { className: "nice-activity-feed__group", children: [
618
+ o && t && /* @__PURE__ */ e("div", { className: "nice-activity-feed__date-label", children: t }),
619
+ l.map((s) => /* @__PURE__ */ c(
620
+ "div",
621
+ {
622
+ className: "nice-activity-feed__event",
623
+ onClick: () => a == null ? void 0 : a(s),
624
+ role: a ? "button" : void 0,
625
+ tabIndex: a ? 0 : void 0,
626
+ onKeyDown: (b) => {
627
+ b.key === "Enter" && a && a(s);
628
+ },
629
+ children: [
630
+ /* @__PURE__ */ e("span", { className: "nice-activity-feed__icon", children: K[s.type] }),
631
+ /* @__PURE__ */ c("div", { className: "nice-activity-feed__body", children: [
632
+ s.actorAvatarUrl && /* @__PURE__ */ e("img", { src: s.actorAvatarUrl, alt: "", className: "nice-activity-feed__avatar" }),
633
+ /* @__PURE__ */ c("span", { className: "nice-activity-feed__message", children: [
634
+ /* @__PURE__ */ e("strong", { children: s.actorName }),
635
+ " ",
636
+ s.message,
637
+ s.targetLabel && /* @__PURE__ */ c(H, { children: [
638
+ " ",
639
+ /* @__PURE__ */ e("em", { children: s.targetLabel })
640
+ ] })
641
+ ] }),
642
+ /* @__PURE__ */ e("time", { className: "nice-activity-feed__time", children: s.timestamp })
643
+ ] })
644
+ ]
645
+ },
646
+ s.id
647
+ ))
648
+ ] }, t || "__all")),
649
+ E && k && /* @__PURE__ */ e("button", { className: "nice-activity-feed__load-more", onClick: k, children: "Load more" })
650
+ ] });
651
+ });
652
+ export {
653
+ se as NiceActivityFeed,
654
+ Z as NiceComments,
655
+ ee as NiceRatings,
656
+ ie as NiceSocialPanel,
657
+ te as NiceTagCloud,
658
+ ne as NiceUserList,
659
+ ae as NiceWiki
660
+ };
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@nice2dev/social",
3
+ "version": "0.1.0",
4
+ "description": "Nice2Dev Social — Comments, ratings, activity feed, tag cloud, social panel, user list, wiki for React",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.mjs",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.cjs"
14
+ },
15
+ "./style.css": "./dist/style.css"
16
+ },
17
+ "files": ["dist", "LICENSE", "CHANGELOG.md", "README.md"],
18
+ "sideEffects": ["*.css"],
19
+ "scripts": {
20
+ "dev": "vite",
21
+ "build": "tsc -p tsconfig.build.json && vite build",
22
+ "typecheck": "tsc --noEmit",
23
+ "test": "vitest run --config vitest.config.ts",
24
+ "test:watch": "vitest --config vitest.config.ts",
25
+ "test:coverage": "vitest run --config vitest.config.ts --coverage",
26
+ "clean": "rimraf dist",
27
+ "prepublishOnly": "npm run build"
28
+ },
29
+ "keywords": [
30
+ "react", "comments", "ratings", "social", "activity", "feed",
31
+ "tags", "wiki", "user-list", "nicetodev"
32
+ ],
33
+ "author": "NiceToDev <contact@nicetodev.com>",
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/NiceToDev/NiceToDev.UI.git",
38
+ "directory": "nice2dev-social"
39
+ },
40
+ "homepage": "https://github.com/NiceToDev/NiceToDev.UI#readme",
41
+ "bugs": {
42
+ "url": "https://github.com/NiceToDev/NiceToDev.UI/issues"
43
+ },
44
+ "peerDependencies": {
45
+ "react": ">=17.0.0",
46
+ "react-dom": ">=17.0.0"
47
+ },
48
+ "devDependencies": {
49
+ "@types/react": "^18.2.0",
50
+ "@types/react-dom": "^18.2.0",
51
+ "@vitejs/plugin-react": "^4.2.0",
52
+ "react": "^18.2.0",
53
+ "react-dom": "^18.2.0",
54
+ "typescript": "^5.3.0",
55
+ "vite": "^6.2.0",
56
+ "vite-plugin-dts": "^4.5.0"
57
+ }
58
+ }