@adea-ai/ui 0.106.2 → 0.108.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 (112) hide show
  1. package/dist/components/composites/appearance-editor/appearance-editor.d.ts +9 -3
  2. package/dist/components/composites/appearance-editor/appearance-editor.d.ts.map +1 -1
  3. package/dist/components/composites/appearance-editor/appearance-editor.js +61 -29
  4. package/dist/components/composites/appearance-editor/appearance-editor.js.map +1 -1
  5. package/dist/components/composites/appearance-editor/appearance-types.d.ts +2 -0
  6. package/dist/components/composites/appearance-editor/appearance-types.d.ts.map +1 -1
  7. package/dist/components/composites/appearance-editor/index.d.ts +1 -1
  8. package/dist/components/composites/appearance-editor/index.d.ts.map +1 -1
  9. package/dist/components/composites/appearance-editor/index.js +2 -2
  10. package/dist/components/layout/contextual-sidebar/contextual-sidebar.d.ts +55 -0
  11. package/dist/components/layout/contextual-sidebar/contextual-sidebar.d.ts.map +1 -0
  12. package/dist/components/layout/contextual-sidebar/contextual-sidebar.js +199 -0
  13. package/dist/components/layout/contextual-sidebar/contextual-sidebar.js.map +1 -0
  14. package/dist/components/layout/contextual-sidebar/index.d.ts +3 -0
  15. package/dist/components/layout/contextual-sidebar/index.d.ts.map +1 -0
  16. package/dist/components/layout/contextual-sidebar/index.js +3 -0
  17. package/dist/components/layout/pixel-resize-handle/index.d.ts +2 -0
  18. package/dist/components/layout/pixel-resize-handle/index.d.ts.map +1 -0
  19. package/dist/components/layout/pixel-resize-handle/index.js +2 -0
  20. package/dist/components/layout/pixel-resize-handle/pixel-resize-handle.d.ts +29 -0
  21. package/dist/components/layout/pixel-resize-handle/pixel-resize-handle.d.ts.map +1 -0
  22. package/dist/components/layout/pixel-resize-handle/pixel-resize-handle.js +183 -0
  23. package/dist/components/layout/pixel-resize-handle/pixel-resize-handle.js.map +1 -0
  24. package/dist/components/layout/sidebar-nav/sidebar-nav-resize-handle.d.ts +3 -19
  25. package/dist/components/layout/sidebar-nav/sidebar-nav-resize-handle.d.ts.map +1 -1
  26. package/dist/components/layout/sidebar-nav/sidebar-nav-resize-handle.js +8 -76
  27. package/dist/components/layout/sidebar-nav/sidebar-nav-resize-handle.js.map +1 -1
  28. package/dist/components/ui/badge/badge.d.ts.map +1 -1
  29. package/dist/components/ui/badge/badge.js +9 -5
  30. package/dist/components/ui/badge/badge.js.map +1 -1
  31. package/dist/components/ui/combobox/combobox.d.ts.map +1 -1
  32. package/dist/components/ui/combobox/combobox.js +2 -2
  33. package/dist/components/ui/combobox/combobox.js.map +1 -1
  34. package/dist/components/ui/context-menu/context-menu.d.ts.map +1 -1
  35. package/dist/components/ui/context-menu/context-menu.js +3 -3
  36. package/dist/components/ui/context-menu/context-menu.js.map +1 -1
  37. package/dist/components/ui/dropdown-menu/dropdown-menu.d.ts.map +1 -1
  38. package/dist/components/ui/dropdown-menu/dropdown-menu.js +7 -4
  39. package/dist/components/ui/dropdown-menu/dropdown-menu.js.map +1 -1
  40. package/dist/components/ui/menubar/menubar.d.ts.map +1 -1
  41. package/dist/components/ui/menubar/menubar.js +3 -3
  42. package/dist/components/ui/menubar/menubar.js.map +1 -1
  43. package/dist/components/ui/popover/popover.d.ts.map +1 -1
  44. package/dist/components/ui/popover/popover.js +2 -2
  45. package/dist/components/ui/popover/popover.js.map +1 -1
  46. package/dist/components/ui/resizable/resizable.d.ts.map +1 -1
  47. package/dist/components/ui/resizable/resizable.js +2 -2
  48. package/dist/components/ui/resizable/resizable.js.map +1 -1
  49. package/dist/components/ui/select/select.d.ts.map +1 -1
  50. package/dist/components/ui/select/select.js +2 -2
  51. package/dist/components/ui/select/select.js.map +1 -1
  52. package/dist/components/ui/sheet/sheet.d.ts +16 -0
  53. package/dist/components/ui/sheet/sheet.d.ts.map +1 -1
  54. package/dist/components/ui/sheet/sheet.js +82 -16
  55. package/dist/components/ui/sheet/sheet.js.map +1 -1
  56. package/dist/index.d.ts +1 -0
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +4 -2
  59. package/dist/lib/overlay.d.ts +15 -0
  60. package/dist/lib/overlay.d.ts.map +1 -1
  61. package/dist/lib/overlay.js +14 -1
  62. package/dist/lib/overlay.js.map +1 -1
  63. package/dist/lint/index.d.ts.map +1 -1
  64. package/dist/lint.js +154 -20
  65. package/dist/lint.js.map +1 -1
  66. package/dist/r/appearance-editor.json +1 -1
  67. package/dist/r/contextual-sidebar.json +25 -0
  68. package/dist/r/pixel-resize-handle.json +23 -0
  69. package/dist/r/registry.json +50 -2
  70. package/dist/r/sidebar-nav.json +1 -1
  71. package/dist/r/src/components/composites/appearance-editor/appearance-editor.tsx +55 -33
  72. package/dist/r/src/components/composites/appearance-editor/appearance-types.ts +2 -0
  73. package/dist/r/src/components/composites/appearance-editor/index.ts +1 -1
  74. package/dist/r/src/components/layout/contextual-sidebar/contextual-sidebar.tsx +180 -0
  75. package/dist/r/src/components/layout/contextual-sidebar/index.ts +6 -0
  76. package/dist/r/src/components/layout/pixel-resize-handle/index.ts +1 -0
  77. package/dist/r/src/components/layout/pixel-resize-handle/pixel-resize-handle.tsx +177 -0
  78. package/dist/r/src/components/layout/sidebar-nav/sidebar-nav-resize-handle.tsx +7 -55
  79. package/dist/r/src/components/ui/badge/badge.tsx +9 -5
  80. package/dist/r/src/components/ui/combobox/combobox.tsx +2 -1
  81. package/dist/r/src/components/ui/context-menu/context-menu.tsx +3 -0
  82. package/dist/r/src/components/ui/dropdown-menu/dropdown-menu.tsx +9 -0
  83. package/dist/r/src/components/ui/menubar/menubar.tsx +3 -0
  84. package/dist/r/src/components/ui/popover/popover.tsx +8 -1
  85. package/dist/r/src/components/ui/resizable/resizable.tsx +4 -2
  86. package/dist/r/src/components/ui/select/select.tsx +2 -1
  87. package/dist/r/src/components/ui/sheet/sheet.tsx +78 -15
  88. package/dist/r/src/lib/overlay.ts +14 -0
  89. package/dist/r/src/styles/base.css +13 -0
  90. package/package.json +3 -1
  91. package/registry.json +50 -2
  92. package/src/components/composites/appearance-editor/appearance-editor.tsx +55 -33
  93. package/src/components/composites/appearance-editor/appearance-types.ts +2 -0
  94. package/src/components/composites/appearance-editor/index.ts +1 -1
  95. package/src/components/layout/contextual-sidebar/contextual-sidebar.tsx +180 -0
  96. package/src/components/layout/contextual-sidebar/index.ts +6 -0
  97. package/src/components/layout/pixel-resize-handle/index.ts +1 -0
  98. package/src/components/layout/pixel-resize-handle/pixel-resize-handle.tsx +177 -0
  99. package/src/components/layout/sidebar-nav/sidebar-nav-resize-handle.tsx +7 -55
  100. package/src/components/ui/badge/badge.tsx +9 -5
  101. package/src/components/ui/combobox/combobox.tsx +2 -1
  102. package/src/components/ui/context-menu/context-menu.tsx +3 -0
  103. package/src/components/ui/dropdown-menu/dropdown-menu.tsx +9 -0
  104. package/src/components/ui/menubar/menubar.tsx +3 -0
  105. package/src/components/ui/popover/popover.tsx +8 -1
  106. package/src/components/ui/resizable/resizable.tsx +4 -2
  107. package/src/components/ui/select/select.tsx +2 -1
  108. package/src/components/ui/sheet/sheet.tsx +78 -15
  109. package/src/index.ts +1 -0
  110. package/src/lib/overlay.ts +14 -0
  111. package/src/lint/index.ts +234 -22
  112. package/src/styles/base.css +13 -0
package/src/lint/index.ts CHANGED
@@ -747,6 +747,185 @@ const staticStringValue = (node: any): string | null => {
747
747
  return null
748
748
  }
749
749
 
750
+ interface ImportedJsxBindings {
751
+ named: WeakSet<object>
752
+ namespaces: WeakSet<object>
753
+ }
754
+
755
+ const isImportedJsxComponent = (
756
+ name: any,
757
+ imports: ImportedJsxBindings,
758
+ context: LintContext,
759
+ namespaceExport?: string
760
+ ): boolean => {
761
+ if (name?.type === 'JSXIdentifier' || name?.type === 'Identifier')
762
+ return isImportedBinding(name, imports.named, context)
763
+ const memberName = name?.property?.name
764
+ return (
765
+ (name?.type === 'JSXMemberExpression' || name?.type === 'MemberExpression') &&
766
+ (name.object?.type === 'JSXIdentifier' || name.object?.type === 'Identifier') &&
767
+ (name.property?.type === 'JSXIdentifier' || name.property?.type === 'Identifier') &&
768
+ (namespaceExport ? memberName === namespaceExport : /^[A-Z]/.test(memberName ?? '')) &&
769
+ isImportedBinding(name.object, imports.namespaces, context)
770
+ )
771
+ }
772
+
773
+ type IconChildKind = 'empty' | 'icon' | 'text' | 'unknown'
774
+
775
+ const CONTENT_OVERRIDE_PROPS = new Set([
776
+ 'children',
777
+ 'dangerouslySetInnerHTML',
778
+ 'innerHTML',
779
+ 'textContent',
780
+ ])
781
+
782
+ /** Opaque spreads and objects containing content props may override children. */
783
+ const spreadMayOverrideContent = (argument: any): boolean => {
784
+ while (
785
+ argument?.type === 'TSAsExpression' ||
786
+ argument?.type === 'TSSatisfiesExpression' ||
787
+ argument?.type === 'TSNonNullExpression'
788
+ )
789
+ argument = argument.expression
790
+ if (argument?.type !== 'ObjectExpression') return true
791
+ return argument.properties.some((property: any) => {
792
+ if (property.type !== 'Property') return true
793
+ const key = staticPropertyKey(property.key, property.computed === true)
794
+ return key === null || CONTENT_OVERRIDE_PROPS.has(key)
795
+ })
796
+ }
797
+
798
+ const hasContentOverride = (openingElement: any): boolean =>
799
+ (openingElement?.attributes ?? []).some((attribute: any) => {
800
+ if (attribute.type === 'JSXSpreadAttribute') return spreadMayOverrideContent(attribute.argument)
801
+ return (
802
+ attribute.type === 'JSXAttribute' &&
803
+ attribute.name?.type === 'JSXIdentifier' &&
804
+ CONTENT_OVERRIDE_PROPS.has(attribute.name.name)
805
+ )
806
+ })
807
+
808
+ const iconChildrenKind = (
809
+ children: any[],
810
+ icons: ImportedJsxBindings,
811
+ context: LintContext
812
+ ): IconChildKind => {
813
+ let hasIcon = false
814
+ for (const child of children) {
815
+ const kind = iconChildKind(child, icons, context)
816
+ if (kind === 'text' || kind === 'unknown') return kind
817
+ if (kind === 'icon') hasIcon = true
818
+ }
819
+ return hasIcon ? 'icon' : 'empty'
820
+ }
821
+
822
+ const SVG_TEXT_CONTAINERS = new Set(['text', 'foreignObject'])
823
+
824
+ /** A raw SVG may be labelled or contain dynamic text instead of an icon. */
825
+ const svgMayContainVisibleText = (children: any[]): boolean => {
826
+ for (const child of children) {
827
+ if (!child) return true
828
+ if (child.type === 'JSXText') {
829
+ if (String(child.value ?? '').trim()) return true
830
+ continue
831
+ }
832
+ if (child.type === 'JSXFragment') {
833
+ if (svgMayContainVisibleText(child.children ?? [])) return true
834
+ continue
835
+ }
836
+ if (child.type === 'JSXExpressionContainer') {
837
+ const expression = child.expression
838
+ if (!expression || expression.type === 'JSXEmptyExpression') continue
839
+ if (expression.type === 'JSXElement' || expression.type === 'JSXFragment') {
840
+ if (
841
+ svgMayContainVisibleText(
842
+ expression.type === 'JSXElement' ? [expression] : (expression.children ?? [])
843
+ )
844
+ )
845
+ return true
846
+ continue
847
+ }
848
+ const text = staticStringValue(expression)
849
+ if (text !== null) {
850
+ if (text.trim()) return true
851
+ continue
852
+ }
853
+ if (
854
+ expression.type === 'Literal' &&
855
+ (expression.value === null || typeof expression.value === 'boolean')
856
+ )
857
+ continue
858
+ return true
859
+ }
860
+ if (child.type !== 'JSXElement') return true
861
+
862
+ const name = child.openingElement?.name
863
+ if (name?.type !== 'JSXIdentifier') return true
864
+ if (SVG_TEXT_CONTAINERS.has(name.name)) return true
865
+ if (hasContentOverride(child.openingElement)) return true
866
+ if (name.name === 'title' || name.name === 'desc') continue
867
+ if (!/^[a-z]/.test(name.name) || name.name.includes('-')) return true
868
+ if (svgMayContainVisibleText(child.children ?? [])) return true
869
+ }
870
+ return false
871
+ }
872
+
873
+ /**
874
+ * Proves only the uncomplicated icon-only forms that can be read without
875
+ * executing Solid expressions: imported Lucide components, raw SVG, fragments,
876
+ * whitespace, and spans that contain only those forms.
877
+ */
878
+ const iconChildKind = (
879
+ child: any,
880
+ icons: ImportedJsxBindings,
881
+ context: LintContext
882
+ ): IconChildKind => {
883
+ if (!child) return 'unknown'
884
+ if (child.type === 'JSXText') return String(child.value ?? '').trim() ? 'text' : 'empty'
885
+ if (child.type === 'JSXFragment') return iconChildrenKind(child.children ?? [], icons, context)
886
+ if (child.type === 'JSXExpressionContainer') {
887
+ const expression = child.expression
888
+ if (!expression || expression.type === 'JSXEmptyExpression') return 'empty'
889
+ if (expression.type === 'JSXElement' || expression.type === 'JSXFragment')
890
+ return iconChildKind(expression, icons, context)
891
+ const text = staticStringValue(expression)
892
+ if (text !== null) return text.trim() ? 'text' : 'empty'
893
+ if (
894
+ expression.type === 'Literal' &&
895
+ (expression.value === null || typeof expression.value === 'boolean')
896
+ )
897
+ return 'empty'
898
+ if (expression.type === 'Literal' && typeof expression.value === 'number') return 'text'
899
+ return 'unknown'
900
+ }
901
+ if (child.type !== 'JSXElement') return 'unknown'
902
+
903
+ const name = child.openingElement?.name
904
+ if (name?.type === 'JSXIdentifier' && name.name === 'svg')
905
+ return hasContentOverride(child.openingElement) ||
906
+ svgMayContainVisibleText(child.children ?? [])
907
+ ? 'unknown'
908
+ : 'icon'
909
+ if (isImportedJsxComponent(name, icons, context))
910
+ return hasContentOverride(child.openingElement) ? 'unknown' : 'icon'
911
+ if (name?.type === 'JSXIdentifier' && name.name === 'span') {
912
+ if (hasContentOverride(child.openingElement)) return 'unknown'
913
+ return iconChildrenKind(child.children ?? [], icons, context)
914
+ }
915
+ return 'unknown'
916
+ }
917
+
918
+ const hasStaticallyIconOnlyChildren = (
919
+ node: any,
920
+ icons: ImportedJsxBindings,
921
+ context: LintContext
922
+ ): boolean => {
923
+ return (
924
+ !hasContentOverride(node.openingElement) &&
925
+ iconChildrenKind(node.children ?? [], icons, context) === 'icon'
926
+ )
927
+ }
928
+
750
929
  const isIconButtonSize = (node: any): boolean =>
751
930
  attributeValues(node, 'size').some((value) => {
752
931
  const size = staticStringValue(value)
@@ -762,64 +941,97 @@ const actionTooltipStatus = (node: any): 'valid' | 'blank' | 'unknown' | 'missin
762
941
  return 'unknown'
763
942
  }
764
943
 
765
- const isImportedComponent = (identifier: any, imports: Set<string>): boolean =>
766
- identifier?.type === 'Identifier' && imports.has(identifier.name)
767
-
768
944
  /** Requires shared icon actions to carry the shared explanatory-tooltip contract. */
769
945
  const requireActionButtonTooltip: RuleModule = {
770
946
  meta: {
771
947
  type: 'problem',
772
948
  docs: {
773
949
  description:
774
- 'Consumer icon-size shared Button actions use ActionButton with a nonblank tooltip; dynamic values remain a review boundary.',
950
+ 'Icon-sized shared Buttons and statically proven icon-only Buttons use ActionButton with a tooltip; icon-only ActionButtons need one too.',
775
951
  },
776
952
  schema: [],
777
953
  messages: {
778
954
  useActionButton:
779
- 'Icon-size Button actions use ActionButton with a nonblank tooltip. Keep the control name in aria-label; the tooltip explains the action.',
955
+ 'Icon-size or icon-only Button actions must use ActionButton with a nonblank tooltip. Keep the control name in aria-label; the tooltip explains the action.',
780
956
  missingTooltip:
781
- 'An icon-size ActionButton needs a supplied nonblank tooltip. Keep its accessible name in aria-label as well.',
957
+ 'An icon-size or icon-only ActionButton needs a supplied nonblank tooltip. Keep its accessible name in aria-label as well.',
782
958
  },
783
959
  },
784
960
  create(context) {
785
- const buttons = new Set<string>()
786
- const actionButtons = new Set<string>()
961
+ const buttons: ImportedJsxBindings = { named: new WeakSet(), namespaces: new WeakSet() }
962
+ const actionButtons: ImportedJsxBindings = { named: new WeakSet(), namespaces: new WeakSet() }
963
+ const icons: ImportedJsxBindings = { named: new WeakSet(), namespaces: new WeakSet() }
787
964
  const recordImports = (node: any) => {
788
965
  const source = node.source?.value
789
966
  const target =
790
967
  source === BUTTON_MODULE ? buttons : source === ACTION_BUTTON_MODULE ? actionButtons : null
968
+ if (source === 'lucide-solid') {
969
+ if (node.importKind === 'type') return
970
+ for (const specifier of node.specifiers ?? []) {
971
+ if (!specifier.local || specifier.importKind === 'type') continue
972
+ if (specifier.type === 'ImportNamespaceSpecifier') icons.namespaces.add(specifier.local)
973
+ else if (specifier.type === 'ImportSpecifier') {
974
+ const imported = specifier.imported?.name ?? specifier.imported?.value
975
+ if (typeof imported === 'string' && /^[A-Z]/.test(imported))
976
+ icons.named.add(specifier.local)
977
+ }
978
+ }
979
+ return
980
+ }
791
981
  if (!target) return
982
+ if (node.importKind === 'type') return
792
983
  for (const specifier of node.specifiers ?? []) {
793
- if (specifier.type !== 'ImportSpecifier') continue
984
+ if (specifier.type === 'ImportNamespaceSpecifier' && specifier.local) {
985
+ target.namespaces.add(specifier.local)
986
+ continue
987
+ }
988
+ if (
989
+ specifier.type !== 'ImportSpecifier' ||
990
+ !specifier.local ||
991
+ specifier.importKind === 'type'
992
+ )
993
+ continue
794
994
  const imported = specifier.imported?.name ?? specifier.imported?.value
795
995
  if (
796
996
  (source === BUTTON_MODULE && imported === 'Button') ||
797
997
  (source === ACTION_BUTTON_MODULE && imported === 'ActionButton')
798
998
  )
799
- target.add(specifier.local?.name)
999
+ target.named.add(specifier.local)
800
1000
  }
801
1001
  }
802
1002
 
803
1003
  return {
804
- ImportDeclaration: recordImports,
805
- JSXOpeningElement(node: any) {
806
- if (!isIconButtonSize(node)) return
807
- const name = node.name?.type === 'JSXIdentifier' ? node.name.name : null
808
- const isButton = name !== null && buttons.has(name)
809
- const isActionButton = name !== null && actionButtons.has(name)
810
- const asValues = attributeValues(node, 'as')
811
- const targetsButton = asValues.some((value) => isImportedComponent(value, buttons))
1004
+ Program(node: any) {
1005
+ for (const statement of node.body ?? [])
1006
+ if (statement.type === 'ImportDeclaration') recordImports(statement)
1007
+ },
1008
+ JSXElement(node: any) {
1009
+ const opening = node.openingElement
1010
+ const isButton = isImportedJsxComponent(opening.name, buttons, context, 'Button')
1011
+ const isActionButton = isImportedJsxComponent(
1012
+ opening.name,
1013
+ actionButtons,
1014
+ context,
1015
+ 'ActionButton'
1016
+ )
1017
+ const asValues = attributeValues(opening, 'as')
1018
+ const targetsButton = asValues.some((value) =>
1019
+ isImportedJsxComponent(value, buttons, context, 'Button')
1020
+ )
812
1021
  const targetsActionButton = asValues.some((value) =>
813
- isImportedComponent(value, actionButtons)
1022
+ isImportedJsxComponent(value, actionButtons, context, 'ActionButton')
814
1023
  )
1024
+ if (!isButton && !isActionButton && !targetsButton && !targetsActionButton) return
1025
+ const iconSized = isIconButtonSize(opening)
1026
+ if (!iconSized && !hasStaticallyIconOnlyChildren(node, icons, context)) return
815
1027
  if (isButton || targetsButton) {
816
- context.report({ node, messageId: 'useActionButton' })
1028
+ context.report({ node: opening, messageId: 'useActionButton' })
817
1029
  return
818
1030
  }
819
1031
  if (!isActionButton && !targetsActionButton) return
820
- const status = actionTooltipStatus(node)
1032
+ const status = actionTooltipStatus(opening)
821
1033
  if (status === 'missing' || status === 'blank')
822
- context.report({ node, messageId: 'missingTooltip' })
1034
+ context.report({ node: opening, messageId: 'missingTooltip' })
823
1035
  },
824
1036
  }
825
1037
  },
@@ -277,6 +277,19 @@
277
277
  text-orientation: mixed;
278
278
  }
279
279
 
280
+ /**
281
+ * The docking box of an inset Sheet: below the top bar, with one gap (two spacing
282
+ * steps) from the top bar, the end edge and the bottom of the window, so the
283
+ * panel fills the height of the main view and its edges line up on every side.
284
+ * Narrow windows keep the same gap at the start edge too.
285
+ */
286
+ @utility panel-dialog-inset {
287
+ top: calc(var(--topbar-height) + var(--spacing) * 2);
288
+ inset-inline-end: calc(var(--spacing) * 2);
289
+ bottom: calc(var(--spacing) * 2);
290
+ max-width: calc(100vw - var(--spacing) * 4);
291
+ }
292
+
280
293
  @utility no-scrollbar {
281
294
  -ms-overflow-style: none;
282
295
  scrollbar-width: none;