diff --git a/Gemfile.lock b/Gemfile.lock
index 8a0fa2569..e2f198321 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -178,7 +178,7 @@ GEM
logger (~> 1.5)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
- concurrent-ruby (1.3.4)
+ concurrent-ruby (1.3.5)
connection_pool (2.5.0)
countries (7.1.0)
unaccent (~> 0.3)
@@ -370,7 +370,7 @@ GEM
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
- logger (1.6.4)
+ logger (1.6.5)
loofah (2.24.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
@@ -431,7 +431,7 @@ GEM
prettier_print (1.2.1)
prop_initializer (0.2.0)
zeitwerk (>= 2.6.18)
- psych (5.2.2)
+ psych (5.2.3)
date
stringio
public_suffix (6.0.1)
@@ -492,7 +492,7 @@ GEM
rb-inotify (0.11.1)
ffi (~> 1.0)
rbs (2.8.4)
- rdoc (6.10.0)
+ rdoc (6.11.0)
psych (>= 4.0.0)
redis (5.3.0)
redis-client (>= 0.22.0)
diff --git a/app/components/avo/discreet_information_component.html.erb b/app/components/avo/discreet_information_component.html.erb
new file mode 100644
index 000000000..a584da6c0
--- /dev/null
+++ b/app/components/avo/discreet_information_component.html.erb
@@ -0,0 +1,7 @@
+
+ <% items.each do |item| %>
+ <%= content_tag element_tag(item), **element_attributes(item), class: "flex gap-1 text-xs font-normal text-gray-600 hover:text-gray-900", title: item.tooltip, data: {tippy: :tooltip, **data(item)} do %>
+ <%= item.label if item.label.present? %> <%= helpers.svg item.icon, class: "text-2xl h-4" %>
+ <% end %>
+ <% end %>
+
diff --git a/app/components/avo/discreet_information_component.rb b/app/components/avo/discreet_information_component.rb
new file mode 100644
index 000000000..b356c22d5
--- /dev/null
+++ b/app/components/avo/discreet_information_component.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class Avo::DiscreetInformationComponent < Avo::BaseComponent
+ prop :payload
+
+ def items
+ @payload.items.compact
+ end
+
+ def element_tag(item)
+ if item.url.present?
+ :a
+ else
+ :div
+ end
+ end
+
+ def element_attributes(item)
+ if item.url.present?
+ {href: item.url, target: item.url_target}
+ else
+ {}
+ end
+ end
+
+ def data(item) = item.data || {}
+end
diff --git a/app/components/avo/items/panel_component.rb b/app/components/avo/items/panel_component.rb
index f8a17cc97..da6791077 100644
--- a/app/components/avo/items/panel_component.rb
+++ b/app/components/avo/items/panel_component.rb
@@ -35,7 +35,8 @@ def args
data: {panel_id: "main"},
cover_photo: @resource.cover_photo,
profile_photo: @resource.profile_photo,
- external_link: @resource.get_external_link
+ external_link: @resource.get_external_link,
+ discreet_information: @resource.discreet_information
}
else
{name: @item.name, description: @item.description, index: @index}
diff --git a/app/components/avo/panel_component.html.erb b/app/components/avo/panel_component.html.erb
index a289ee8bc..5b6e9fb4f 100644
--- a/app/components/avo/panel_component.html.erb
+++ b/app/components/avo/panel_component.html.erb
@@ -5,7 +5,8 @@
description: @description,
display_breadcrumbs: @display_breadcrumbs,
profile_photo: @profile_photo,
- external_link: @external_link
+ external_link: @external_link,
+ discreet_information: @discreet_information
) do |header| %>
<% if name_slot.present? %>
<% header.with_name_slot do %>
diff --git a/app/components/avo/panel_component.rb b/app/components/avo/panel_component.rb
index c725d49b4..131ff23da 100644
--- a/app/components/avo/panel_component.rb
+++ b/app/components/avo/panel_component.rb
@@ -19,16 +19,17 @@ class Avo::PanelComponent < Avo::BaseComponent
prop :body_classes
prop :data, default: {}.freeze
prop :display_breadcrumbs, default: false
+ prop :discreet_information
prop :index
prop :classes
prop :profile_photo
prop :cover_photo
prop :args, kind: :**, default: {}.freeze
+ prop :external_link
def after_initialize
@name = @args.dig(:name) || @args.dig(:title)
end
- prop :external_link
def classes
class_names(@classes, "has-cover-photo": @cover_photo.present?, "has-profile-photo": @profile_photo.present?)
diff --git a/app/components/avo/panel_header_component.html.erb b/app/components/avo/panel_header_component.html.erb
index 7ff8ad993..be42a8a39 100644
--- a/app/components/avo/panel_header_component.html.erb
+++ b/app/components/avo/panel_header_component.html.erb
@@ -19,6 +19,9 @@
<%= svg "heroicons/outline/arrow-top-right-on-square", class: "ml-2 text-2xl h-4" %>
<% end %>
<% end %>
+ <% if @discreet_information.present? %>
+ <%= render Avo::DiscreetInformationComponent.new(payload: @discreet_information) %>
+ <% end %>
<% end %>
<% end %>
<% end %>
diff --git a/app/components/avo/panel_header_component.rb b/app/components/avo/panel_header_component.rb
index 168cc60c2..ca358a625 100644
--- a/app/components/avo/panel_header_component.rb
+++ b/app/components/avo/panel_header_component.rb
@@ -10,6 +10,7 @@ class Avo::PanelHeaderComponent < Avo::BaseComponent
prop :external_link
prop :description
prop :display_breadcrumbs, default: false
+ prop :discreet_information
prop :profile_photo
private
diff --git a/app/helpers/avo/turbo_stream_actions_helper.rb b/app/helpers/avo/turbo_stream_actions_helper.rb
index a274f02dc..c888e3530 100644
--- a/app/helpers/avo/turbo_stream_actions_helper.rb
+++ b/app/helpers/avo/turbo_stream_actions_helper.rb
@@ -15,6 +15,10 @@ def avo_close_modal
target: Avo::MODAL_FRAME_ID,
template: @view_context.turbo_frame_tag(Avo::MODAL_FRAME_ID)
end
+
+ def avo_turbo_reload
+ turbo_stream_action_tag :turbo_reload
+ end
end
end
diff --git a/app/javascript/avo.base.js b/app/javascript/avo.base.js
index 0e36a174f..127aa9112 100644
--- a/app/javascript/avo.base.js
+++ b/app/javascript/avo.base.js
@@ -25,7 +25,7 @@ Mousetrap.bind('r r r', () => {
// Cpture scroll position
scrollTop = document.scrollingElement.scrollTop
- Turbo.visit(window.location.href, { action: 'replace' })
+ window.StreamActions.turbo_reload()
})
function isMac() {
@@ -56,6 +56,7 @@ document.addEventListener('keyup', (event) => {
function initTippy() {
tippy('[data-tippy="tooltip"]', {
theme: 'light',
+ allowHTML: true,
content(reference) {
const title = reference.getAttribute('title')
reference.removeAttribute('title')
diff --git a/app/javascript/js/custom-stream-actions.js b/app/javascript/js/custom-stream-actions.js
index 205f5b965..bab75873d 100644
--- a/app/javascript/js/custom-stream-actions.js
+++ b/app/javascript/js/custom-stream-actions.js
@@ -8,6 +8,11 @@ StreamActions.close_filters_dropdown = function () {
document.querySelector('.filters-dropdown-selector').classList.add('hidden')
}
+// Uses Turbo to refresh the page
+StreamActions.turbo_reload = function () {
+ window.Turbo.visit(window.location.href, { action: 'replace' })
+}
+
StreamActions.open_filter = function () {
const id = this.getAttribute('unique-id')
setTimeout(() => {
diff --git a/app/views/avo/actions/show.html.erb b/app/views/avo/actions/show.html.erb
index c56ce68fa..9f8ef5ca6 100644
--- a/app/views/avo/actions/show.html.erb
+++ b/app/views/avo/actions/show.html.erb
@@ -5,7 +5,7 @@
<%= turbo_frame_tag Avo::MODAL_FRAME_ID do %>
"
- data-action-no-confirmation-value="<%= @action.no_confirmation %>"
+ data-action-no-confirmation-value="<%= @action.no_confirmation? %>"
data-action-resource-name-value="<%= @resource.model_key %>"
data-resource-id="<%= params[:id] %>"
class="hidden text-slate-800"
diff --git a/lib/avo/base_action.rb b/lib/avo/base_action.rb
index 53641a6b8..3b31de11b 100644
--- a/lib/avo/base_action.rb
+++ b/lib/avo/base_action.rb
@@ -4,6 +4,8 @@ class BaseAction
include Avo::Concerns::HasActionStimulusControllers
include Avo::Concerns::Hydration
+ DATA_ATTRIBUTES = {turbo_frame: Avo::MODAL_FRAME_ID}
+
class_attribute :name, default: nil
class_attribute :message
class_attribute :confirm_button_label
@@ -52,8 +54,8 @@ def to_param
to_s
end
- def link_arguments(resource:, arguments: {}, **args)
- path = Avo::Services::URIService.parse(resource.record&.persisted? ? resource.record_path : resource.records_path)
+ def path(resource:, arguments: {}, **args)
+ Avo::Services::URIService.parse(resource.record&.persisted? ? resource.record_path : resource.records_path)
.append_paths("actions")
.append_query(
**{
@@ -63,8 +65,10 @@ def link_arguments(resource:, arguments: {}, **args)
}.compact
)
.to_s
+ end
- [path, {turbo_frame: Avo::MODAL_FRAME_ID}]
+ def link_arguments(resource:, arguments: {}, **args)
+ [path(resource:, arguments:, **args), DATA_ATTRIBUTES]
end
# Encrypt the arguments so we can pass sensible data as a query param.
@@ -365,6 +369,16 @@ def disabled?
!enabled?
end
+ def no_confirmation?
+ Avo::ExecutionContext.new(
+ target: no_confirmation,
+ action: self,
+ resource: @resource,
+ view: @view,
+ arguments:
+ ).handle
+ end
+
private
def add_message(body, type = :info)
diff --git a/lib/avo/concerns/has_discreet_information.rb b/lib/avo/concerns/has_discreet_information.rb
new file mode 100644
index 000000000..6b521b75f
--- /dev/null
+++ b/lib/avo/concerns/has_discreet_information.rb
@@ -0,0 +1,15 @@
+module Avo
+ module Concerns
+ module HasDiscreetInformation
+ extend ActiveSupport::Concern
+
+ included do
+ class_attribute :discreet_information, instance_accessor: false
+ end
+
+ def discreet_information
+ ::Avo::DiscreetInformation.new resource: self
+ end
+ end
+ end
+end
diff --git a/lib/avo/discreet_information.rb b/lib/avo/discreet_information.rb
new file mode 100644
index 000000000..3e56f7f3d
--- /dev/null
+++ b/lib/avo/discreet_information.rb
@@ -0,0 +1,62 @@
+class Avo::DiscreetInformation
+ extend PropInitializer::Properties
+ include ActionView::Helpers::TagHelper
+
+ prop :resource, reader: :public
+
+ delegate :record, :view, to: :resource
+
+ def items
+ Array.wrap(resource.class.discreet_information).map do |item|
+ if item == :timestamps
+ timestamp_item(item)
+ else
+ parse_payload(item)
+ end
+ end
+ end
+
+ private
+
+ def timestamp_item(item)
+ return if record.created_at.blank? && record.updated_at.blank?
+
+ time_format = "%Y-%m-%d %H:%M:%S"
+ created_at = record.created_at.strftime(time_format)
+ updated_at = record.updated_at.strftime(time_format)
+
+ created_at_tag = if record.created_at.present?
+ I18n.t("avo.created_at_timestamp", created_at:)
+ end
+
+ updated_at_tag = if record.updated_at.present?
+ I18n.t("avo.updated_at_timestamp", updated_at:)
+ end
+
+ DiscreetInformationItem.new(
+ tooltip: tag.div([created_at_tag, updated_at_tag].compact.join(tag.br), style: "text-align: right;"),
+ icon: "heroicons/outline/clock"
+ )
+ end
+
+ def parse_payload(item)
+ return unless item.is_a?(Hash)
+
+ args = {
+ record:,
+ resource:,
+ view:
+ }
+
+ DiscreetInformationItem.new(
+ tooltip: Avo::ExecutionContext.new(target: item[:tooltip], **args).handle,
+ icon: Avo::ExecutionContext.new(target: item[:icon], **args).handle,
+ url: Avo::ExecutionContext.new(target: item[:url], **args).handle,
+ url_target: Avo::ExecutionContext.new(target: item[:url_target], **args).handle,
+ data: Avo::ExecutionContext.new(target: item[:data], **args).handle,
+ label: Avo::ExecutionContext.new(target: item[:label], **args).handle
+ )
+ end
+
+ DiscreetInformationItem = Struct.new(:tooltip, :icon, :url, :url_target, :data, :label, keyword_init: true) unless defined?(DiscreetInformationItem)
+end
diff --git a/lib/avo/execution_context.rb b/lib/avo/execution_context.rb
index d3b8657c8..dedb47740 100644
--- a/lib/avo/execution_context.rb
+++ b/lib/avo/execution_context.rb
@@ -1,4 +1,7 @@
module Avo
+ # = Avo Execution Context
+ #
+ # The ExecutionContext class is used to evaluate blocks in isolation.
class ExecutionContext
include Avo::Concerns::HasHelpers
@@ -36,7 +39,32 @@ def initialize(**args)
delegate :result, to: :card
delegate :authorize, to: Avo::Services::AuthorizationService
- # Return target if target is not callable, otherwise, execute target on this instance context
+ # Executes the target and returns the result.
+ # It takes in a target which usually is a block. If it's something else, it will return it.
+ #
+ # It automatically has access to the view context, current user, request, main app, avo, locale, and params.
+ # It also has a +delegate_missing_to+ which allows it to delegate missing methods to the view context for a more natural experience.
+ # You may pass extra arguments to the initialize method to have them available in the block that will be executed.
+ # You may pass extra modules to extend the class with.
+ #
+ # ==== Examples
+ #
+ # ===== Normal use
+ #
+ # Avo::ExecutionContext.new(target: -> { "Hello, world!" }).handle
+ # => "Hello, world!"
+ #
+ # ===== Providing a record
+ #
+ # Avo::ExecutionContext.new(target: -> { record.name }, record: @record).handle
+ # => "John Doe"
+ #
+ # ===== Providing a module
+ #
+ # This will include the SanitizeHelper module in the class and so have the +sanitize+ method available.
+ #
+ # Avo::ExecutionContext.new(target: -> { sanitize "#{record.name}" } record: @record, include: [ActionView::Helpers::SanitizeHelper]).handle
+ # => "John Doe"
def handle
target.respond_to?(:call) ? instance_exec(&target) : target
end
diff --git a/lib/avo/resources/base.rb b/lib/avo/resources/base.rb
index 4b3b342e4..be7fd37f5 100644
--- a/lib/avo/resources/base.rb
+++ b/lib/avo/resources/base.rb
@@ -16,6 +16,7 @@ class Base
include Avo::Concerns::Hydration
include Avo::Concerns::Pagination
include Avo::Concerns::ControlsPlacement
+ include Avo::Concerns::HasDiscreetInformation
# Avo::Current methods
delegate :context, to: Avo::Current
diff --git a/spec/dummy/app/avo/actions/toggle_published.rb b/spec/dummy/app/avo/actions/toggle_published.rb
index 03e4d9802..9f0b136bf 100644
--- a/spec/dummy/app/avo/actions/toggle_published.rb
+++ b/spec/dummy/app/avo/actions/toggle_published.rb
@@ -3,6 +3,7 @@ class Avo::Actions::TogglePublished < Avo::BaseAction
self.message = "Are you sure, sure?"
self.confirm_button_label = "Toggle"
self.cancel_button_label = "Don't toggle yet"
+ self.no_confirmation = -> { arguments[:no_confirmation] || false }
def fields
field :notify_user, as: :boolean, default: true
diff --git a/spec/dummy/app/avo/resources/event.rb b/spec/dummy/app/avo/resources/event.rb
index 0d2301a91..7ac3dfd6f 100644
--- a/spec/dummy/app/avo/resources/event.rb
+++ b/spec/dummy/app/avo/resources/event.rb
@@ -17,6 +17,7 @@ class Avo::Resources::Event < Avo::BaseResource
self.profile_photo = {
source: :profile_photo
}
+ self.discreet_information = :timestamps
def fields
field :name, as: :text, link_to_record: true, sortable: true, stacked: true
diff --git a/spec/dummy/app/avo/resources/post.rb b/spec/dummy/app/avo/resources/post.rb
index c7a89c8e5..a84b25c99 100644
--- a/spec/dummy/app/avo/resources/post.rb
+++ b/spec/dummy/app/avo/resources/post.rb
@@ -33,6 +33,28 @@ class Avo::Resources::Post < Avo::BaseResource
main_app.post_path(record)
}
+ self.discreet_information = [
+ :timestamps,
+ {
+ tooltip: -> { sanitize("Product is #{record.published_at ? "published" : "draft"}", tags: %w[strong]) },
+ icon: -> { "heroicons/outline/#{record.published_at ? "eye" : "eye-slash"}" }
+ },
+ {
+ label: -> { record.published_at ? "✅" : "🙄" },
+ tooltip: -> { "Post is #{record.published_at ? "published" : "draft"}. Click to toggle." },
+ url: -> {
+ Avo::Actions::TogglePublished.path(
+ resource: resource,
+ arguments: {
+ records: Array.wrap(record.id),
+ no_confirmation: true
+ }
+ )
+ },
+ data: Avo::BaseAction::DATA_ATTRIBUTES,
+ }
+ ]
+
def fields
field :id, as: :id
field :name, required: true, sortable: true
diff --git a/spec/dummy/app/avo/resources/product.rb b/spec/dummy/app/avo/resources/product.rb
index 442bf323a..92e60daee 100644
--- a/spec/dummy/app/avo/resources/product.rb
+++ b/spec/dummy/app/avo/resources/product.rb
@@ -5,12 +5,12 @@ class Avo::Resources::Product < Avo::BaseResource
self.grid_view = {
card: -> do
{
- cover_url: record.image.attached? ? main_app.url_for(record.image.variant(resize: "300x300")) : nil,
+ cover_url: record.image.attached? ? main_app.url_for(record.image.variant(resize_to_fill: [300, 300])) : nil,
title: record.title,
body: simple_format(record.description),
- badge_label: (record.updated_at < 1.week.ago) ? "New" : "Updated",
- badge_color: (record.updated_at < 1.week.ago) ? "green" : "orange",
- badge_title: (record.updated_at < 1.week.ago) ? "New product here" : "Updated product here"
+ badge_label: (record.status == :new) ? "New" : "Updated",
+ badge_color: (record.status == :new) ? "green" : "orange",
+ badge_title: (record.status == :new) ? "New product here" : "Updated product here"
}
end,
html: -> do
@@ -28,6 +28,16 @@ class Avo::Resources::Product < Avo::BaseResource
self.index_query = -> {
query.includes image_attachment: :blob
}
+ self.discreet_information = [
+ {
+ tooltip: -> { sanitize("Product is #{record.status}", tags: %w[strong]) },
+ icon: "heroicons/outline/bold"
+ },
+ :timestamps
+ ]
+ self.profile_photo = {
+ source: -> { record.image.attached? ? main_app.url_for(record.image.variant(resize_to_fill: [300, 300])) : nil }
+ }
def fields
field :id, as: :id
diff --git a/spec/dummy/app/models/product.rb b/spec/dummy/app/models/product.rb
index 1cc7d59cc..9afca7e86 100644
--- a/spec/dummy/app/models/product.rb
+++ b/spec/dummy/app/models/product.rb
@@ -31,4 +31,8 @@ class Product < ApplicationRecord
has_one_attached :image
has_many_attached :images
+
+ def status
+ (id % 2).zero? ? :new : :updated
+ end
end
diff --git a/spec/dummy/config/locales/avo.en.yml b/spec/dummy/config/locales/avo.en.yml
index 94c103638..daba5030b 100644
--- a/spec/dummy/config/locales/avo.en.yml
+++ b/spec/dummy/config/locales/avo.en.yml
@@ -35,6 +35,8 @@ en:
delete_item: Delete %{item}
detach_item: detach %{item}
details: details
+ created_at_timestamp: "Created at %{created_at}"
+ updated_at_timestamp: "Updated at %{updated_at}"
download: Download
download_file: Download file
download_item: Download %{item}
diff --git a/yarn.lock b/yarn.lock
index 8aaff3b01..76ca2c8fb 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1504,9 +1504,9 @@
prettier ">=2.3.0"
"@rails/actioncable@^7.0":
- version "7.0.4"
- resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-7.0.4.tgz#70a3ca56809f7aaabb80af2f9c01ae51e1a8ed41"
- integrity sha512-tz4oM+Zn9CYsvtyicsa/AwzKZKL+ITHWkhiu7x+xF77clh2b4Rm+s6xnOgY/sGDWoFWZmtKsE95hxBPkgQQNnQ==
+ version "7.2.201"
+ resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-7.2.201.tgz#bfb3da01b3e2462f5a18f372c52dedd7de76037f"
+ integrity sha512-wsTdWoZ5EfG5k3t7ORdyQF0ZmDEgN4aVPCanHAiNEwCROqibSZMXXmCbH7IDJUVri4FOeAVwwbPINI7HVHPKBw==
"@rails/activestorage@^6.1.710":
version "6.1.710"
@@ -5103,8 +5103,16 @@ stimulus-use@^0.50.0:
dependencies:
hotkeys-js ">=3"
-"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0:
- name string-width-cjs
+"string-width-cjs@npm:string-width@^4.2.0":
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
+string-width@^4.1.0, string-width@^4.2.0:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -5204,8 +5212,14 @@ string.prototype.trimstart@^1.0.8:
define-properties "^1.2.1"
es-object-atoms "^1.0.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
- name strip-ansi-cjs
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -5605,8 +5619,16 @@ which@^2.0.1:
dependencies:
isexe "^2.0.0"
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
- name wrap-ansi-cjs
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
+wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==