@proteos/ui 0.52.0 → 0.53.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proteos/ui",
3
- "version": "0.52.0",
3
+ "version": "0.53.0",
4
4
  "license": "FSL-1.1-Apache-2.0",
5
5
  "description": "Proteos React component library (React Aria Components + Tailwind v4).",
6
6
  "repository": {
@@ -56,7 +56,7 @@
56
56
  "tailwind-variants": "^3.2.2",
57
57
  "tailwindcss": "^4.2.4",
58
58
  "thinking-orbs": "^0.3.1",
59
- "@proteos/sdk": "0.52.0"
59
+ "@proteos/sdk": "0.53.0"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@biomejs/biome": "^2.4.13",
@@ -0,0 +1,12 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { ATTRIBUTE_TYPE_ICONS, ATTRIBUTE_TYPES } from './attribute-type-icon'
4
+
5
+ describe('ATTRIBUTE_TYPE_ICONS', () => {
6
+ it('maps every attribute type to an icon (a missing entry crashes the type picker at runtime)', () => {
7
+ for (const type of ATTRIBUTE_TYPES) {
8
+ expect(ATTRIBUTE_TYPE_ICONS[type], `no icon for ${type}`).toBeTypeOf('object')
9
+ }
10
+ expect(ATTRIBUTE_TYPES).toContain('contact-address')
11
+ })
12
+ })
@@ -5,6 +5,7 @@ import {
5
5
  Brackets,
6
6
  Calendar,
7
7
  CircleDot,
8
+ Contact,
8
9
  Hash,
9
10
  Link2,
10
11
  type LucideIcon,
@@ -55,6 +56,9 @@ import { cn } from '../../lib/cn'
55
56
  currency → Banknote (money — amount + currency_code)
56
57
  knowledge-text → BookText (long text stored as a knowledge node)
57
58
  file → Paperclip (attachment — stored-file reference)
59
+ contact-address → Contact (one canonical reachable address — email /
60
+ phone / LinkedIn — binding the record to a
61
+ conversation contact)
58
62
  ========================================================================= */
59
63
 
60
64
  /**
@@ -81,6 +85,7 @@ export const AttributeType = {
81
85
  Currency: 'currency',
82
86
  KnowledgeText: 'knowledge-text',
83
87
  File: 'file',
88
+ ContactAddress: 'contact-address',
84
89
  } as const
85
90
  export type AttributeType = (typeof AttributeType)[keyof typeof AttributeType]
86
91
 
@@ -102,6 +107,7 @@ export const ATTRIBUTE_TYPE_ICONS: Record<AttributeType, LucideIcon> = {
102
107
  [AttributeType.Currency]: Banknote,
103
108
  [AttributeType.KnowledgeText]: BookText,
104
109
  [AttributeType.File]: Paperclip,
110
+ [AttributeType.ContactAddress]: Contact,
105
111
  }
106
112
 
107
113
  export function useAttributeTypeIcon(type: AttributeType): LucideIcon {
@@ -11,13 +11,31 @@ describe('operatorsForType', () => {
11
11
  }
12
12
  })
13
13
 
14
- it('only allows starts_with / ends_with on strings', () => {
14
+ it('only allows starts_with / ends_with on string-like types', () => {
15
15
  for (const op of ['starts_with', 'ends_with'] as const) {
16
16
  const types = OPERATORS.find((o) => o.id === op)?.types ?? []
17
- expect(types).toEqual(['string'])
17
+ expect(types).toEqual(['string', 'contact-address'])
18
18
  }
19
19
  })
20
20
 
21
+ it('treats contact-address like a string (one canonical value)', () => {
22
+ const ops = operatorsForType('contact-address').map((o) => o.id)
23
+ for (const expected of [
24
+ 'eq',
25
+ 'ne',
26
+ 'in',
27
+ 'not_in',
28
+ 'contains',
29
+ 'starts_with',
30
+ 'ends_with',
31
+ 'empty',
32
+ 'not_empty',
33
+ ]) {
34
+ expect(ops).toContain(expected)
35
+ }
36
+ expect(ops).not.toContain('gt')
37
+ })
38
+
21
39
  it('allows in / not_in on strings, numbers, and enums (not datetime/boolean/array)', () => {
22
40
  for (const op of ['in', 'not_in'] as const) {
23
41
  const types = new Set(OPERATORS.find((o) => o.id === op)?.types ?? [])
@@ -18,44 +18,126 @@ export const OPERATORS: ReadonlyArray<OperatorDescriptor> = [
18
18
  id: 'eq',
19
19
  label: 'is',
20
20
  arity: 1,
21
- types: ['string', 'number', 'integer', 'currency', 'datetime', 'enum', 'boolean', 'relation', 'user', 'principal'],
21
+ types: [
22
+ 'string',
23
+ 'number',
24
+ 'integer',
25
+ 'currency',
26
+ 'datetime',
27
+ 'enum',
28
+ 'boolean',
29
+ 'relation',
30
+ 'user',
31
+ 'principal',
32
+ 'contact-address',
33
+ ],
22
34
  },
23
35
  {
24
36
  id: 'ne',
25
37
  label: 'is not',
26
38
  arity: 1,
27
- types: ['string', 'number', 'integer', 'currency', 'datetime', 'enum', 'boolean', 'relation', 'user', 'principal'],
39
+ types: [
40
+ 'string',
41
+ 'number',
42
+ 'integer',
43
+ 'currency',
44
+ 'datetime',
45
+ 'enum',
46
+ 'boolean',
47
+ 'relation',
48
+ 'user',
49
+ 'principal',
50
+ 'contact-address',
51
+ ],
28
52
  },
29
53
  {
30
54
  id: 'in',
31
55
  label: 'is any of',
32
56
  arity: 'n',
33
- types: ['string', 'number', 'integer', 'currency', 'enum', 'relation', 'user', 'principal'],
57
+ types: [
58
+ 'string',
59
+ 'number',
60
+ 'integer',
61
+ 'currency',
62
+ 'enum',
63
+ 'relation',
64
+ 'user',
65
+ 'principal',
66
+ 'contact-address',
67
+ ],
34
68
  },
35
69
  {
36
70
  id: 'not_in',
37
71
  label: 'is none of',
38
72
  arity: 'n',
39
- types: ['string', 'number', 'integer', 'currency', 'enum', 'relation', 'user', 'principal'],
73
+ types: [
74
+ 'string',
75
+ 'number',
76
+ 'integer',
77
+ 'currency',
78
+ 'enum',
79
+ 'relation',
80
+ 'user',
81
+ 'principal',
82
+ 'contact-address',
83
+ ],
84
+ },
85
+ { id: 'contains', label: 'contains', arity: 1, types: ['string', 'array', 'contact-address'] },
86
+ { id: 'starts_with', label: 'starts with', arity: 1, types: ['string', 'contact-address'] },
87
+ { id: 'ends_with', label: 'ends with', arity: 1, types: ['string', 'contact-address'] },
88
+ {
89
+ id: 'gt',
90
+ label: 'is greater than',
91
+ arity: 1,
92
+ types: ['number', 'integer', 'currency', 'datetime'],
40
93
  },
41
- { id: 'contains', label: 'contains', arity: 1, types: ['string', 'array'] },
42
- { id: 'starts_with', label: 'starts with', arity: 1, types: ['string'] },
43
- { id: 'ends_with', label: 'ends with', arity: 1, types: ['string'] },
44
- { id: 'gt', label: 'is greater than', arity: 1, types: ['number', 'integer', 'currency', 'datetime'] },
45
94
  { id: 'gte', label: 'is on or after', arity: 1, types: ['datetime'] },
46
- { id: 'lt', label: 'is less than', arity: 1, types: ['number', 'integer', 'currency', 'datetime'] },
95
+ {
96
+ id: 'lt',
97
+ label: 'is less than',
98
+ arity: 1,
99
+ types: ['number', 'integer', 'currency', 'datetime'],
100
+ },
47
101
  { id: 'lte', label: 'is on or before', arity: 1, types: ['datetime'] },
48
102
  {
49
103
  id: 'empty',
50
104
  label: 'is empty',
51
105
  arity: 0,
52
- types: ['string', 'number', 'integer', 'currency', 'datetime', 'enum', 'boolean', 'array', 'relation', 'user', 'principal', 'file'],
106
+ types: [
107
+ 'string',
108
+ 'number',
109
+ 'integer',
110
+ 'currency',
111
+ 'datetime',
112
+ 'enum',
113
+ 'boolean',
114
+ 'array',
115
+ 'relation',
116
+ 'user',
117
+ 'principal',
118
+ 'file',
119
+ 'contact-address',
120
+ ],
53
121
  },
54
122
  {
55
123
  id: 'not_empty',
56
124
  label: 'is not empty',
57
125
  arity: 0,
58
- types: ['string', 'number', 'integer', 'currency', 'datetime', 'enum', 'boolean', 'array', 'relation', 'user', 'principal', 'file'],
126
+ types: [
127
+ 'string',
128
+ 'number',
129
+ 'integer',
130
+ 'currency',
131
+ 'datetime',
132
+ 'enum',
133
+ 'boolean',
134
+ 'array',
135
+ 'relation',
136
+ 'user',
137
+ 'principal',
138
+ 'file',
139
+ 'contact-address',
140
+ ],
59
141
  },
60
142
  ]
61
143