playbook_ui 16.6.0.pre.alpha.play294216115 → 16.6.0.pre.alpha.play294216129

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f4decbfeb1e045070d591f56daf85d3ef2794bf2855d33e889f00fb2dc694be7
4
- data.tar.gz: 04b363904b41e8d0b30e2752af2c1e07d6cf6ca38ca5ae0f3576b515fcaa8c23
3
+ metadata.gz: 1e05534a3f97a66180278f383ceebd88308b9ab9e457d114db537950d5635b1d
4
+ data.tar.gz: 39a298031f506647efb1e3236753c31dac0f2ee077f1c680d0ccb7f5762a6fba
5
5
  SHA512:
6
- metadata.gz: 42918e61a6d599a6b5033053eaad2a5d09139d2d977d85f766f0fbd5c65f0e05608bef3c57fa787ce7efd3e847d785ba198472bd532e436ffbc2d7c161ad3314
7
- data.tar.gz: 13bc1c4045f5d400052204a4a495d345aeb2b814de5ba3d8c87e510327e18ecaa80d18d99b307d75a9d3e8a7c2cf9c2e0b2466ea7a30c41f9d282aad0766275f
6
+ metadata.gz: 04b9269da508cd3c91abf55b85c2d077e0fd49b56e68448e2a89c642ae5c3d0bf0dba05d0541c8f7363960dacc67129f44f72969805f88e1d87da54f15879b3c
7
+ data.tar.gz: e61a63d3488d8c7adb0123a3b0c4aba424ab2a220b48ed176881a8e5af9feb8dcb3847b11feb47c1a774a96b39fc91580c6bbef4f6f4dadda9f820649452654c
@@ -26,6 +26,24 @@ const DropdownWithConstrainHeight = (props) => {
26
26
  options={options}
27
27
  {...props}
28
28
  />
29
+
30
+ <br />
31
+
32
+ <Dropdown
33
+ label="Subcomponent With Constrain Height"
34
+ options={options}
35
+ {...props}
36
+ >
37
+ <Dropdown.Trigger />
38
+ <Dropdown.Container constrainHeight>
39
+ {options.map((option) => (
40
+ <Dropdown.Option
41
+ key={option.id}
42
+ option={option}
43
+ />
44
+ ))}
45
+ </Dropdown.Container>
46
+ </Dropdown>
29
47
  </>
30
48
  )
31
49
  }
@@ -18,3 +18,14 @@
18
18
  constrain_height: true,
19
19
  label: "With Constrain Height"
20
20
  }) %>
21
+
22
+ <br>
23
+
24
+ <%= pb_rails("dropdown", props: {options: options, label: "Subcomponent With Constrain Height"}) do %>
25
+ <%= pb_rails("dropdown/dropdown_trigger") %>
26
+ <%= pb_rails("dropdown/dropdown_container", props: { constrain_height: true }) do %>
27
+ <% options.each do |option| %>
28
+ <%= pb_rails("dropdown/dropdown_option", props: {option: option}) %>
29
+ <% end %>
30
+ <% end %>
31
+ <% end %>
@@ -1,9 +1,14 @@
1
- import React, { ReactSVGElement } from 'react'
1
+ import React, { ReactSVGElement, useEffect, useState } from 'react'
2
2
  import classnames from 'classnames'
3
3
  import { buildAriaProps, buildDataProps, buildHtmlProps } from '../utilities/props'
4
4
  import { GlobalProps, globalProps } from '../utilities/globalProps'
5
5
  import { isValidEmoji } from '../utilities/validEmojiChecker'
6
- import { getPlaybookIconClassName, supportsPlaybookIcon } from '../utilities/icons/playbookIconResolver'
6
+ import {
7
+ getPlaybookIconClassName,
8
+ loadPlaybookIconSvg,
9
+ supportsPlaybookIcon,
10
+ supportsPlaybookIconFetch,
11
+ } from '../utilities/icons/playbookIconResolver'
7
12
 
8
13
  export type IconSizes = "lg"
9
14
  | "xs"
@@ -119,6 +124,38 @@ declare global {
119
124
  var PB_ICONS: {[key: string]: React.FunctionComponent<any>}
120
125
  }
121
126
 
127
+ const svgAttributeMap: {[key: string]: string} = {
128
+ 'clip-path': 'clipPath',
129
+ 'clip-rule': 'clipRule',
130
+ 'fill-rule': 'fillRule',
131
+ 'stroke-linecap': 'strokeLinecap',
132
+ 'stroke-linejoin': 'strokeLinejoin',
133
+ 'stroke-width': 'strokeWidth',
134
+ 'xmlns:xlink': 'xmlnsXlink',
135
+ 'xlink:href': 'xlinkHref',
136
+ }
137
+
138
+ const convertAttributeName = (attributeName: string) => {
139
+ return svgAttributeMap[attributeName] || attributeName.replace(/-([a-z])/g, (_match, letter) => letter.toUpperCase())
140
+ }
141
+
142
+ const parseSvgAttributes = (attributeSource: string) => {
143
+ const attributes: {[key: string]: string} = {}
144
+ const attributePattern = /([:@a-zA-Z0-9-]+)="([^"]*)"/g
145
+ let match = attributePattern.exec(attributeSource)
146
+
147
+ while (match) {
148
+ attributes[convertAttributeName(match[1])] = match[2]
149
+ match = attributePattern.exec(attributeSource)
150
+ }
151
+
152
+ return attributes
153
+ }
154
+
155
+ const normalizeSvgInnerMarkup = (innerMarkup: string, fillColor: string) => {
156
+ return innerMarkup.replace(/<(path)\b([^>]*)fill="[^"]*"/gi, `<$1$2fill="${fillColor}"`)
157
+ }
158
+
122
159
  const Icon = (props: IconProps) => {
123
160
  const {
124
161
  aria = {},
@@ -145,9 +182,35 @@ const Icon = (props: IconProps) => {
145
182
 
146
183
  let iconElement: ReactSVGElement | null = typeof(icon) === "object" ? icon : null
147
184
  const iconName = typeof(icon) === "string" ? icon : ""
148
- const legacyPowerIcon = !customIcon && !iconElement && window.PB_ICONS ? window.PB_ICONS[iconName] : null
149
- const hasBuiltInPlaybookIcon = !legacyPowerIcon && !customIcon && !iconElement && Boolean(iconName) && supportsPlaybookIcon(iconName)
150
- const playbookIconClassName = hasBuiltInPlaybookIcon ? getPlaybookIconClassName(iconName) : null
185
+ const shouldLoadInlinePlaybookIcon = !customIcon && !iconElement && Boolean(iconName) && supportsPlaybookIconFetch(iconName)
186
+ const [inlinePlaybookSvgMarkup, setInlinePlaybookSvgMarkup] = useState<string | null | undefined>(undefined)
187
+
188
+ useEffect(() => {
189
+ let isActive = true
190
+
191
+ if (!shouldLoadInlinePlaybookIcon) {
192
+ setInlinePlaybookSvgMarkup(undefined)
193
+ return () => {
194
+ isActive = false
195
+ }
196
+ }
197
+
198
+ setInlinePlaybookSvgMarkup(undefined)
199
+
200
+ loadPlaybookIconSvg(iconName).then((markup) => {
201
+ if (isActive) setInlinePlaybookSvgMarkup(markup)
202
+ })
203
+
204
+ return () => {
205
+ isActive = false
206
+ }
207
+ }, [iconName, shouldLoadInlinePlaybookIcon])
208
+
209
+ const inlinePlaybookSvgReady = Boolean(inlinePlaybookSvgMarkup)
210
+ const inlinePlaybookSvgFailed = shouldLoadInlinePlaybookIcon && inlinePlaybookSvgMarkup === null
211
+ const legacyPowerIcon = !customIcon && !iconElement && (!shouldLoadInlinePlaybookIcon || inlinePlaybookSvgFailed) && window.PB_ICONS ? window.PB_ICONS[iconName] : null
212
+ const hasClassBasedPlaybookIcon = !inlinePlaybookSvgReady && !legacyPowerIcon && !customIcon && !iconElement && !shouldLoadInlinePlaybookIcon && Boolean(iconName) && supportsPlaybookIcon(iconName)
213
+ const playbookIconClassName = hasClassBasedPlaybookIcon ? getPlaybookIconClassName(iconName) : null
151
214
 
152
215
  const faClasses = {
153
216
  'fa-border': border,
@@ -161,7 +224,7 @@ const Icon = (props: IconProps) => {
161
224
  [`fa-rotate-${rotation}`]: rotation,
162
225
  }
163
226
 
164
- if (!customIcon && !iconElement && !hasBuiltInPlaybookIcon) {
227
+ if (!customIcon && !iconElement && !inlinePlaybookSvgReady && !hasClassBasedPlaybookIcon) {
165
228
  if (legacyPowerIcon) {
166
229
  const LegacyPowerIcon = legacyPowerIcon
167
230
  iconElement = <LegacyPowerIcon /> as ReactSVGElement
@@ -170,13 +233,13 @@ const Icon = (props: IconProps) => {
170
233
  }
171
234
  }
172
235
 
173
- const isFA = !iconElement && !customIcon && !hasBuiltInPlaybookIcon
236
+ const isFA = !iconElement && !customIcon && !inlinePlaybookSvgReady && !hasClassBasedPlaybookIcon && !shouldLoadInlinePlaybookIcon
174
237
 
175
238
  let classes = classnames(
176
- (!iconElement && !customIcon && !hasBuiltInPlaybookIcon) ? 'pb_icon_kit' : '',
177
- (iconElement || customIcon || hasBuiltInPlaybookIcon) ? 'pb_custom_icon' : fontStyle,
178
- (iconElement || hasBuiltInPlaybookIcon) ? 'svg-inline--fa' : '',
179
- hasBuiltInPlaybookIcon ? 'pb_playbook_icon' : '',
239
+ (!iconElement && !customIcon && !inlinePlaybookSvgReady && !hasClassBasedPlaybookIcon) ? 'pb_icon_kit' : '',
240
+ (iconElement || customIcon || inlinePlaybookSvgReady || hasClassBasedPlaybookIcon) ? 'pb_custom_icon' : fontStyle,
241
+ (iconElement || inlinePlaybookSvgReady || hasClassBasedPlaybookIcon) ? 'svg-inline--fa' : '',
242
+ hasClassBasedPlaybookIcon ? 'pb_playbook_icon' : '',
180
243
  playbookIconClassName,
181
244
  color ? `color_${color}` : '',
182
245
  globalProps(props),
@@ -230,7 +293,29 @@ const Icon = (props: IconProps) => {
230
293
 
231
294
  // Add a conditional here to show only the SVG if custom
232
295
  const displaySVG = (customIcon: any) => {
233
- if (hasBuiltInPlaybookIcon)
296
+ if (inlinePlaybookSvgReady) {
297
+ const svgMatch = inlinePlaybookSvgMarkup?.match(/<svg([^>]*)>([\s\S]*)<\/svg>/i)
298
+ if (!svgMatch) return null
299
+
300
+ const [, rawAttributes, innerMarkup] = svgMatch
301
+ const svgAttributes = parseSvgAttributes(rawAttributes)
302
+
303
+ return (
304
+ <svg
305
+ {...svgAttributes}
306
+ {...ariaProps}
307
+ {...dataProps}
308
+ {...htmlProps}
309
+ className={classes}
310
+ color={color || 'currentColor'}
311
+ dangerouslySetInnerHTML={{ __html: normalizeSvgInnerMarkup(innerMarkup.trim(), color || 'currentColor') }}
312
+ height="auto"
313
+ id={id}
314
+ width="auto"
315
+ {...(props.tabIndex !== undefined && { tabIndex })}
316
+ />
317
+ )
318
+ } else if (hasClassBasedPlaybookIcon)
234
319
  return (
235
320
  <i
236
321
  {...ariaProps}
@@ -272,6 +357,8 @@ const Icon = (props: IconProps) => {
272
357
  </span>
273
358
  </>
274
359
  )
360
+ else if (shouldLoadInlinePlaybookIcon)
361
+ return null
275
362
  else
276
363
  return (
277
364
  <>
@@ -156,6 +156,28 @@ module Playbook
156
156
  class << self
157
157
  @cache_mutex = Mutex.new
158
158
 
159
+ def resolved_icon_name(icon_name)
160
+ return icon_name unless icon_alias_map
161
+ return icon_name if icon_name.nil?
162
+
163
+ aliases = icon_alias_map[icon_name]
164
+ return icon_name unless aliases
165
+
166
+ if aliases.is_a?(Array)
167
+ aliases.find { |alias_name| icon_path_index.key?(alias_name) } || icon_name
168
+ else
169
+ aliases
170
+ end
171
+ end
172
+
173
+ def resolved_icon_path(icon_name)
174
+ return nil unless Rails.application.config.respond_to?(:icon_path)
175
+
176
+ resolved_name = resolved_icon_name(icon_name)
177
+ path = icon_path_index[resolved_name]
178
+ path if path && File.exist?(path)
179
+ end
180
+
159
181
  # Cache aliases.json across the process, but invalidate when the file changes (dev-safe)
160
182
  def icon_alias_map
161
183
  return @icon_alias_map if alias_cache_fresh?
@@ -296,17 +318,7 @@ module Playbook
296
318
  private
297
319
 
298
320
  def resolve_alias(icon)
299
- return icon unless icon_alias_map
300
- return icon if icon.nil?
301
-
302
- aliases = icon_alias_map[icon]
303
- return icon unless aliases
304
-
305
- if aliases.is_a?(Array)
306
- aliases.find { |alias_name| file_exists?(alias_name) } || icon
307
- else
308
- aliases
309
- end
321
+ self.class.resolved_icon_name(icon)
310
322
  end
311
323
 
312
324
  def file_exists?(alias_name)